From 7fa1422021a7eed5229d329280054e7df0a72a32 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:00:56 +0800 Subject: [PATCH 01/19] =?UTF-8?q?=E5=86=99=E5=8D=A1(=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5presentation=E5=B1=82=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=B9):=20=E5=A4=84=E7=BD=AE=E8=AE=B0?= =?UTF-8?q?=E5=BD=95+=E9=9C=80=E6=B1=82=E5=8D=A1=E7=89=87v1.0+=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E6=9D=A1=E7=9B=AE=E6=8E=A8=E8=BF=9B=E2=80=94=E2=80=94?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E9=97=AD=E5=90=88(2026-11-04=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=94=A8=E6=88=B7=E4=B8=A4=E8=A3=81:=E4=B8=89?= =?UTF-8?q?=E5=86=85=E9=83=A8=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=85=AC=E5=BC=80=E5=8C=96+=E5=9B=9B=E6=97=8F=E4=B8=80?= =?UTF-8?q?=E6=89=B9=E5=81=9A=E5=AE=8C);=20=E6=95=B0=E5=AD=97=E6=8D=A2?= =?UTF-8?q?=E4=BB=A3227=E2=86=92443=E8=A1=8C/14=E6=96=87=E4=BB=B6=E5=9B=9B?= =?UTF-8?q?=E6=97=8F(A1=E6=95=B0=E6=8D=AE=E7=B1=BB206=E8=A1=8C42=E5=AD=97?= =?UTF-8?q?=E6=AE=B5/A2=E6=8B=86=E4=BB=B6=E5=AD=90=E4=BB=B662=E8=A1=8C/B?= =?UTF-8?q?=E7=BB=84=E5=90=88=E6=A0=B9169=E8=A1=8C80=E5=90=8D=E6=88=90?= =?UTF-8?q?=E5=AF=B9=E8=BD=AC=E5=8F=91=E6=A2=AF/C=E6=9D=82=E9=A1=B96?= =?UTF-8?q?=E8=A1=8C);=20B=E6=97=8F=E8=AE=BE=E8=AE=A1=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=85=AD=E6=9D=A1(=E5=88=86=E6=96=87=E4=BB=B6=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E5=85=AC=E5=BC=80=E5=8C=96+=E6=8E=A5=E7=BA=BFCallable?= =?UTF-8?q?=E6=9D=9F+main=E6=9C=8D=E5=8A=A1=E5=85=AC=E5=BC=80=E5=8C=96+?= =?UTF-8?q?=E5=8F=A5=E6=9F=84=E8=AF=BB=E6=8A=95=E5=BD=B1=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8F=A3+on=5F*=E5=A4=84=E7=90=86=E5=99=A8=E4=BF=9D=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E6=8E=A2=E9=92=88=E9=9B=B6=E6=B3=A2=E5=8F=8A+main.gd?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E4=BE=8B=E5=A4=96=E7=94=B3=E6=8A=A5);=20?= =?UTF-8?q?=E5=8D=A1=E9=9D=A216=E8=8A=82(=E4=BA=94T=E5=8D=A1=E7=B2=BE?= =?UTF-8?q?=E7=A1=AE4.125=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=8C=BA=E9=97=B43.5-?= =?UTF-8?q?5.25/=E5=AF=B9=E7=85=A7=E8=A1=A8R1-R7=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E8=A1=8C=E9=80=90=E5=AD=97=E5=8F=96=E5=9C=A8=E5=86=8C=E5=90=8D?= =?UTF-8?q?/=E9=9B=B6=E5=BE=85=E8=A3=81=E7=82=B9=E5=BC=80=E5=B7=A5?= =?UTF-8?q?=E9=97=A8U0/=E6=96=B0=E5=A2=9Esweep=E6=B5=8B=E8=AF=95=E4=BB=B61?= =?UTF-8?q?=E4=BB=B6/board=5Fview=E8=B4=B4=E9=A1=B6=E9=9B=B6=E5=87=80?= =?UTF-8?q?=E5=A2=9E);=20=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE=E6=8D=A2?= =?UTF-8?q?=E4=BB=A3(=E6=96=87=E6=A1=A3=E8=AE=BE=E8=AE=A1=E5=B7=B2?= =?UTF-8?q?=E9=97=AD=E5=90=88+=E5=AE=9E=E5=86=B5=E5=87=AD=E6=8D=AE+?= =?UTF-8?q?=E7=BC=BA=E5=8F=A3=E5=B1=9E=E6=80=A7=E5=88=A0);=20pipeline=5Fga?= =?UTF-8?q?te=20PASS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../处置-presentation层封装修复-2026-11-04.md | 109 ++++++++ ...架构审查presentation层封装修复批-2026-11-04.md | 238 ++++++++++++++++++ 待办清单.md | 6 +- 3 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 reports/处置-presentation层封装修复-2026-11-04.md create mode 100644 reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md diff --git a/reports/处置-presentation层封装修复-2026-11-04.md b/reports/处置-presentation层封装修复-2026-11-04.md new file mode 100644 index 00000000..d3eba28b --- /dev/null +++ b/reports/处置-presentation层封装修复-2026-11-04.md @@ -0,0 +1,109 @@ +# 处置:presentation 层封装修复批(2026-11-04) + +> **性质**:设计讨论段过程稿(裁决记录+取证事实)。**接收方**=reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md + 待办清单条目「架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移」。**寿命**:需求卡片成立即退役(吸收即死——结论吸收进卡片,未决迁待办清单)。 +> **输入**:reports/审计-全库架构审查-2026-10-29.md §5.1 扩面普查(presentation 层私取面,初读约 227 行)+ 2026-11-04 设计讨论会话用户逐项裁决 + 2026-11-04 会话逐文件取证(worktree archpres-card,基点 61995733,复核零漂移)。 + +## 一、总判据(用户 2026-11-04 裁,本批一切设计遵循) + +1. **修复框架=每处先答「该不该访问」,再定公开形态**:访问该存在的,把被访问方需要暴露的东西做成正式公开面(表达操作或契约的公开方法/公开数据成员),访问方改走公开面;访问不该存在的,改设计(逻辑归位、依赖交付),禁把违规调法直接改名合法化。 +2. **三个内部数据类按登记表定位(改法A,用户裁)**:它们是只被宿主文件填读的登记表,字段转公开数据成员。 +3. **全部一批做完(方案一,用户裁)**:四族修法一张卡、一个实现批、一次合回。 +4. **零行为变更**:同 core 5.1 批口径——玩家可观测面逐位不变;职责归属零改动(代码位置不动,只改往来方式)。 +5. **修复依据**=code-conventions §1.6(拆分交接定义+封装边界:任何代码不得访问其他对象的下划线成员,跨对象往来只走公开方法与属性)。 + +## 二、数字换代(2026-11-04 实查,取代初读口径) + +初读「约 227 行/8 文件」**作废**。现行口径=剔除注释行与 `self._` 后的码行计数:**443 行/14 文件**。取证 grep:`grep -rnE '\._[a-zA-Z]' game/presentation --include='*.gd'` 后剔除注释行与 `self._`;行号与逐行账以写卡段复核为准(5.1 卡同款义务)。 + +| 文件 | 码行 | 归族 | +|---|---|---| +| views/level_map_screen.gd | 88 | A1(87)+C(1,UiKit) | +| main_ui_build.gd | 70 | B | +| views/battle_end_overlay.gd | 69 | A1 | +| views/standee_body.gd | 55 | A2 | +| views/debug_overlay.gd | 50 | A1 | +| main.gd | 45 | B | +| main_adventure_flow.gd | 26 | B | +| main_overlay_flow.gd | 15 | B | +| main_debug_flow.gd | 11 | B | +| views/board_view.gd | 7 | A2 | +| views/settings_overlay.gd | 2 | C(UiKit) | +| views/cell_mark_overlay.gd | 2 | C | +| main_esc_flow.gd | 2 | B | +| ui_assembler.gd | 1 | C(UiKit) | + +四族合计:A1=206 行/42 字段;A2=62 行/16 字段+6 成员;B=169 行/80 名字;C=6 行/4 名字。 + +## 三、逐族裁决 + +### A1·三个内部数据类——字段公开化(用户裁改法A) + +| 宿主文件 | 内部类 | 被宿主摸的字段(42 个,今日逐文件 grep 提取) | +|---|---|---| +| views/level_map_screen.gd:37 `_MapState` | 实例 `_map`(:59) | _abandon_timer _attr_value _bonus_row _bonus_value _canvas _drawn_disp _feat_value _gp_value _hp_bar _hp_value _layer_decor _level_label _runner _title | +| views/battle_end_overlay.gd:15 `_EndOverlayState` | 实例 `st`(:32) | _board_rect _bonus_name _bonus_row_l _bonus_val _cele _crest_bricks _dim _fx_counts _ks_name _ks_row _ks_val _rows_box _seal _verdict | +| views/debug_overlay.gd:16 `_DebugState` | 实例 `st`(:33) | _atk _atk_val _base_atk _base_hp _ctx_locked _db _eff _hp _hp_val _run_data _run_labels _sim_count _sim_out _sim_running | + +裁决与依据:三个类只被宿主文件填读(全 presentation 域 grep `_MapState|_EndOverlayState|_DebugState|._map\.`,文件外消费者**零命中**,2026-11-04 实证),内容全是宿主的界面控件引用与运行数值,没有需要保护的处理规则——按改法A,42 个字段去下划线转公开数据成员,宿主 206 处访问点随改。字段公开后访问本就合法,**规约零改动**。内类自用、宿主不碰的字段维持下划线不动。附回归义务:三个类的「文件外零消费者」落成 sweep 断言。 + +### A2·拆件子件——构造交付+行为归位+读投影 + +**standee_body.gd → st(standee_vitals.gd 实例,9 字段)**,逐字段归属(2026-11-04 逐行判读): + +| 字段 | 现役访问 | 归属 | +|---|---|---| +| _kind | :39 写(生成时);:265/:275/:300/:309/:365/:409 读 | 身份——构造期一次交付;读走公开读法 | +| _display_name / _enemy_id / _armor / _hp_max | :82-85/:96 写(配置/重置时);:48/:53/:66-69/:340-342/:419/:465-482 读 | 同上——构造/配置公开口一次交付;读走公开读法 | +| _order | :131 写;:410 读 | 同上 | +| _dead | :173/:262 写(击杀/复活);:91/:98/:138/:171/:248/:277 读 | 生命周期行为——归位为子件公开方法(标记死亡/复位),宿主停写 | +| _hp_cur | :419 读 | 子件自管(宿主零直写)——读走公开读法 | +| _hp_flash_tw | :254-258 宿主自建 tween 改宿主自身 modulate | 误放子件的宿主动画句柄——**归位回 standee_body 成员**(成员 10→11,预算内) | + +**standee_body.gd → mo(standee_motion.gd 实例,7 字段)**:_hero_state :40 写(生成时置 IDLE)→ 走既有公开方法 apply_hero_state(:300 在役先例);_intent_visible/_glyph/_col/_scale/_rot 写 :140/:145/:159-161/:181、读 :58/:431-458 → 意图显示是演出行为——归位为子件公开方法(显示/隐藏/复位),绘制所需状态经一个公开读取口交付(宿主 _draw 消费,:439-458 现役);_portrait_sprite :284/:431 读、生成期挂载 → 挂载走公开口,绘制读取走公开读法。 + +**board_view.gd → _hover_comp(board_hover.gd 实例,6 成员/7 行)**::249 每战重绑 `_minesweeper` → 公开重绑口(bind_save_manager 先例);:260 复位 `_hover`、:309 `_refresh_hover_after_paint()`、:464 `_hover_at`、:479 `_detect_chord`、:146 连接 `_on_shell_mouse_exited`、:828 读 `_hover` → 各自公开化(悬停操作本来就是该件对外职责,公开化=给应有的身份)。 + +### B·组合根与五个分文件——正式交接面(169 行/80 名) + +**结构判定(2026-11-04 实查)**:main 与五个分文件(main_ui_build/main_adventure_flow/main_overlay_flow/main_debug_flow/main_esc_flow——main 拆出的分步执行件,main 侧留同名单行转发)之间是**成对的单行转发**关系:分文件把信号接到 main 的 `_on_xxx` 处理器,main 里该处理器一行转调分文件同名方法(例:main_adventure_flow.gd:12 `connect(root._on_adventure_finished)` ↔ main.gd:234 `MainAdventureFlow._on_adventure_finished(self, summary)`)。需求是真的(互相需要对方的服务),错在服务没有正式公开身份。 + +**设计规则(六条,全部落卡)**: + +1. **分文件入口方法公开化**:main 转发调到的 `MainXxxFlow._方法` 去下划线公开化(45 行方向)。各分文件公开方法现值 0,公开化后 3–15 个,全部在默认阈值 ≤20 内。 +2. **main 的 on_* 处理器保持私有**(探针/测试按 main._on_* 名字引用 104 处,保名=测量面零波及);分文件接线语句里的 `connect(root._on_xxx)` 改收 **Callable 束**:main 调用分文件接线入口时以参数交付处理器束(键=用途名,值=main 自建 Callable——main 内自引合法),分文件 connect 语句消费束值。引用束注入先例=architecture.md:124 BoardInstaller、5.1 卡束形态。 +3. **main 的服务方法被分文件调的,去下划线公开化**(_refresh_presentations 11 次、_sync_status_entry 5 次、_fatal_screen 4 次、_quit_game 2 次、_begin_session 2 次、_start_debug_levels、_check_board_hints、_show_board_fx_for_node、_replay_locked_click、_sync_settlement_visuals、_wire_battle、_sync_debug_overlay、_open_settings、_open_save_select、_status_data、_gp_summary_text、_tune_sim 等)。 +4. **main 的句柄成员被分文件读写的,成员保持私有,补公开读投影/设置方法**(BreakpointStore 先例):_camp_pres(24 次)、_bonus(9 次)、_debug(6 次)、_confirm_consumer(4 次)+_confirm_consumer_slot、_treasure_ui(3 次)、_seed_value、_current_master_seed 等读写面逐名落卡。 +5. **探针/测试独占、互调不涉及的名字零触碰**(_is_settling 等)——测量面直构既有例外维持。 +6. **main.gd 指标例外申报**:公开方法现值 1,规则 3+4 落地后预计 25–30(T1 实数)——组合根服务面超默认阈值 ≤20,理由与先例=level_runner.gd(公开方法 33 在册基线);基线落法=实现批 `python tools/gdstyle_gate.py --refresh` 人工专用通道。 + +80 名完整清单=写卡段逐名 grep 实核落卡(本次会话已提取:main 侧被摸 60 名+分文件侧被摸 20 名,按上述规则 1–5 逐名归位)。 + +### C·杂项(6 行) + +- **UiKit 三个私有工厂方法被跨文件调**:_gradient_base(level_map_screen.gd:69)、_beams_rect(ui_assembler.gd:106)、_switch_toggle(settings_overlay.gd:88/:105)→ 去下划线公开化(无状态工厂本来就是公用设施,公开化=给应有的身份)。 +- **cell_mark_overlay.gd:13-14 摸宿主 board_view 的 _glow_layer**:board_view 是贴顶件(基线在册 27 公开方法/31 成员,禁再增面)→ 走**构造交付**:board_view 建 cell_mark 时把标记遮罩下发口(引用或 Callable)一并交付,cell_mark 持自有引用,宿主零新增公开面。 + +## 四、S13 预算核查(触件现值与预期,2026-11-04 实测) + +| 触件 | 公开方法现值 | 成员现值 | 本批预期 | +|---|---|---|---| +| main.gd | 1 | 15(默认顶) | 公开方法升 25–30——**指标例外申报**(规则 B6);成员零增 | +| 五分文件 | 各 0 | 各 0 | 公开方法 3–15,预算内 | +| level_map_screen / battle_end_overlay / debug_overlay | 8/6/19 | 7/5/4 | A1 只改字段名,两键零变化 | +| standee_body | 20(顶) | 10 | 公开方法零增;成员 10→11(_hp_flash_tw 归位),预算内 | +| standee_vitals / standee_motion | 1/6 | 12/11 | 行为归位+读投影公开方法净增(各 ≤20 内);成员零增 | +| board_view | 27(基线在册) | 31(基线在册) | **零净增**(C 族走构造交付;A2 走 board_hover 公开面) | +| board_hover | 0 | 9 | 公开方法 +6,预算内 | +| ui_kit / cell_mark_overlay | 0/5 | 0/13 | ui_kit +3;cell_mark 成员 +1(构造交付的引用),预算内 | + +基线在册 5 键(tools/gdstyle_baseline.txt)中 board_view 两键与本批相关:达成「只降不升」=board_view 零新增面;main.gd 例外键经申报落基线。 + +## 五、探针与测试连带面(2026-11-04 实测) + +`grep -rE 'main\._[a-zA-Z]' game/probe game/tests`=**104 处引用/36 个名字/14 个文件**(ui_probe、system_test.gd、ui_input_probe、shot_probe、layout_probe、ui_tune_probe、ui_struct_probe、screen_flow_probe、t17_defer_probe、ui_click_probe、fx_shot_probe、probe_ui_util、unit/presentation/pacing_queue_test、unit/core/breakpoint_resume_test)。设计规则 B2 保 on_* 处理器名不变 → 连带替换面收敛为「探针引用名字 ∩ 公开化名字」的交集(预计 10–20 处,T1 落精确底账);替换属「随被测改动同步的引用形态迁移」,按测试削弱申报登记,断言内容零改动。 + +## 六、验证框架 + +- **结构性主证**:presentation 私取 sweep 测试(新增,1 件)——四族逐组「扫描面非空前置+私取模式零命中+新公开面正锚」,手法沿 5.1 卡 encapsulation_sweep_test(复用源 token 扫描手法不并件,同 5.1 卡 :257 论证)。 +- **零行为变更证明**:探针套件与渲染 golden 全程零转红(呈现面逐位同)+冒烟/单元既有套件零转红+探针/测试替换后当轮真实跑绿。 +- **能力清单**:本批零新增能力(零行为变更,玩家可观测能力面零变化),能力行全部「既有」,`game/tests/README.md` 零编辑。 diff --git a/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md b/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md new file mode 100644 index 00000000..86fe9377 --- /dev/null +++ b/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md @@ -0,0 +1,238 @@ +# 需求卡片:架构审查 presentation 层封装修复批(2026-11-04) + +来源:待办清单「架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移」条目 presentation 层私取面部分(**唯一事实源**:reports/处置-presentation层封装修复-2026-11-04.md——处置记录与本卡并行成文,本卡开工时 T1 首步核其已落盘且与本卡第 3 节逐条一致,冲突即 L3 停卡。设计闭合凭据=2026-11-04 设计讨论会话用户逐项裁决〔改法A=三个内部数据类字段公开化;方案一=四族一批做完〕+审计报告 presentation 普查+code-conventions §1.6)。验收档:**中**(零行为变更批——做错的后果玩家可见但不损持久状态:存档/RNG/结算/数值零触碰)。改动面:游戏代码(含随逻辑改的测试代码)。 + +## 1. 目标与完成定义 + +**这批做什么**:把 presentation 层 14 文件 443 行跨对象下划线私取按四族修法全部落码——封装收口、零行为变更: + +| 族 | 处置对象 | 现状 | 终态 | +|---|---|---|---| +| A1 | 三个内部数据类(level_map_screen `_MapState`/battle_end_overlay `_EndOverlayState`/debug_overlay `_DebugState`) | 宿主直读写内类私有字段 206 行/42 字段 | 42 字段去下划线转公开数据成员,宿主访问点随改;文件外零消费者落 sweep 断言 | +| A2 | 拆件子件(standee_vitals/standee_motion/board_hover)及其宿主(standee_body/board_view) | 宿主直读写子件私有成员 62 行 | 身份数据构造/配置期一次交付;行为归位子件公开方法;读走公开读法;board_hover 6 成员公开化 | +| B | 组合根 main ↔ 五个分文件 | 双向私取 169 行/80 名字(成对单行转发梯) | 分文件入口公开化;接线改收 Callable 束;main 服务方法公开化+句柄读投影/设置口;main on_* 处理器保私有(探针零波及);main.gd 指标例外申报 | +| C | UiKit 私有工厂被外调;cell_mark_overlay 摸宿主 _glow_layer | 6 行 | UiKit 三方法公开化;cell_mark 走构造交付(board_view 零净增) | + +**什么状态算完**:四族每处改动落码、都有第 8 节对照表配对的验收断言当轮真实跑批通过;结构性证据=presentation 私取 sweep 恒零(扫描面非空前置+新公开面正锚);零行为变更证据=探针套件与渲染 golden 全程零转红+冒烟/单元既有套件零转红+探针/测试替换后当轮真实跑绿;代码域闸全绿(冒烟+单元+探针+渲染/静态);两份 reports/ 产出物(实现报告+反哺点清单)写齐。 + +## 2. 设计事实源 + +- **处置记录(输入合同,逐条一致)**:`reports/处置-presentation层封装修复-2026-11-04.md`——四族裁决、字段/名字清单、设计规则六条、S13 预算核查、探针连带面。本卡第 3 节现状与第 7 节落法是其机械承载;T1 复核两件一致(冲突=上游缺陷,L3 停卡)。 +- **审计背景**:`reports/审计-全库架构审查-2026-10-29.md` §5.1 扩面普查(presentation 私取面;行号已换代,以本卡第 3 节实查为准)。 +- **法律**:`design/engineering/code-conventions.md` §1.6(拆分交接定义+封装边界——本批修复依据)。 +- **架构锚点**:`design/engineering/architecture/architecture.md` 呈现层节(:167 起——main 组合根、五分文件、ui_registry、views 各行);:124(BoardInstaller 引用束注入先例——B 族 Callable 接线束依据)。 + +## 3. 现状事实(2026-11-04 worktree archpres-card 实查,基点 61995733,开工自行复核) + +### 3.1 私取面盘点 + +口径=`grep -rnE '\._[a-zA-Z]' game/presentation --include='*.gd'` 剔除注释行与 `self._`:**443 行/14 文件**(T1 以同 grep 重跑落底账)。逐文件行数与归族、A1 三类 42 字段名单、A2 逐字段归属、B 族 80 名处置规则、C 族 4 名——**全部以处置记录第二、三节为准**(逐条一致义务,本节不复制)。关键锚点: + +- A1:level_map_screen.gd:37(内类)/ :59(实例)/ :100(`_map._level_label = …` 形态样例);battle_end_overlay.gd:15/:32;debug_overlay.gd:16/:33。 +- A2:standee_body.gd:39-40(生成期直写)/ :82-85(配置直写)/ :173/:262(_dead 直写)/ :254-258(_hp_flash_tw 宿主 tween 误放子件)/ :419(_hp_cur 只读);board_view.gd:146/:249/:260/:309/:464/:479/:828(hover 六成员)。 +- B:main_adventure_flow.gd:12(`connect(root._on_adventure_finished)`)↔ main.gd:234(一行转发 `MainAdventureFlow._on_adventure_finished(self, summary)`)——成对转发梯样例;main_ui_build.gd:64(`root._treasure_ui = …` 直写句柄);main.gd:180-495(45 行转发面)。 +- C:level_map_screen.gd:69+ui_assembler.gd:106+settings_overlay.gd:88/:105(UiKit 私有工厂);cell_mark_overlay.gd:13-14(`_owner._glow_layer`)。 + +### 3.2 写 T 卡依据的其他现状 + +- **S13 现值(2026-11-04 实测)**:main.gd 公开方法 1/成员 15(默认顶);五分文件公开 0;standee_body 公开 20(顶)/成员 10;board_view 公开 27/成员 31(两键在册基线,**贴顶禁再增**);standee_vitals 1/12;standee_motion 6/11;board_hover 0/9;ui_kit 0/0;cell_mark_overlay 5/13;三 A1 宿主 8-7/6-5/19-4。基线在册 5 键见 tools/gdstyle_baseline.txt。 +- **探针/测试连带**:`main._` 引用 104 处/36 名/14 文件(ui_probe、system_test.gd、ui_input_probe、shot_probe、layout_probe、ui_tune_probe、ui_struct_probe、screen_flow_probe、t17_defer_probe、ui_click_probe、fx_shot_probe、probe_ui_util、unit/presentation/pacing_queue_test、unit/core/breakpoint_resume_test)。规则 B2 保 on_* 处理器名 → 替换面=「引用名字 ∩ 公开化名字」,T1 落精确清单。 +- **mo 既有公开方法先例**:standee_body.gd:300 `mo.apply_hero_state(state, …)` 在役——A2 归位沿同型。 + +## 4. 设计契约(违反即返工红线) + +- 🔴 **逐条一致**:本卡与处置记录逐条一致;冲突=上游缺陷停卡退写卡段(L3)。 +- 🔴 **零行为变更**:呈现逐位同(渲染 golden 零转红)、RNG 消费序零触碰、数值表零触碰、信号顺序逐位同;玩家可观测面零变化。 +- 🔴 **公开形态三规则**(用户的「先判该不该、再定形态」框架落法):①公开**数据成员**仅限 A1 三类 42 名(登记表定位);②A2 子件字段保持私有,一律走构造/配置交付、行为方法、读投影;③main 句柄成员保持私有,走读投影/设置方法对——**任何族禁「顺手把字段改公开」替代设计**。 +- 🔴 **main on_* 处理器保私有**:探针/测试 104 处引用面零改名;分文件经 Callable 束取得处理器。 +- 🔴 **禁留壳**:去下划线公开化后禁留旧私有名别名/转发壳;main 侧既有单行转发层**保留**(非新增——探针 main.X 路径契约承载,反哺点登记进架构)。 +- 🔴 **贴顶件零净增**:board_view(27/31 基线在册)公开方法与成员零净增;standee_body 公开方法零增(成员 10→11 仅 _hp_flash_tw 归位);main.gd 成员零增。 +- 🔴 **S13/S9**:全部新增公开方法签名注解完整(S9 R3);main.gd 例外键按第 14 节申报落基线,实现段禁自行豁免。 +- 🔴 **测试纪律**:禁变异类测试、禁撤销类取证;禁删断言/放宽阈值换绿(第 8 节申报清单之外零删改)。 + +## 5. 范围边界 + +**收件**=第 1 节表四族全部(含调用点改址、探针/测试交集名字替换、新增 sweep 测试件、main.gd 例外键落基线)。 + +**不收件**(去向已指认): +- core 层五处封装修复——reports/需求卡片-架构审查5.1封装修复批-2026-11-03.md(另批,文件面零交叠无避让义务)。 +- 探针/测试的测量面直构惯例与受控例外——零清扫(仅「引用名字 ∩ 公开化名字」交集随批替换)。 +- 架构结构零改动:组合根与五分文件的职责划分、信号接线所有权(连接语句留在分文件)、三数据类的内类形态全部不动;ui_registry/ModalFlow 零触碰。 +- 闸件(static_scan.sh 等)零改动;MC/模拟零跑(无数值改动);`game/tests/README.md` 零编辑(零行为变更=能力清单零新增)。 + +## 6. 关联文档同步面 + +实现批禁写 `design/` 整个目录与根规则文件;下表每条落文义务进 `reports/iter-反哺点-<日期>.md`,由用户裁决后归文档批落文: + +| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务(反哺点清单逐条承载) | +|---|---|---| +| design/engineering/architecture/architecture.md | 呈现层节:main 组合根行+公开服务/句柄面新增登记;五分文件行+公开入口面;level_map_screen/battle_end_overlay/debug_overlay 行+内部数据类定位句(字段公开的登记表);ui_kit 行+三工厂公开化;standee_vitals/standee_motion/board_hover 行+新公开面 | 反哺点清单逐条列行号与改后登记内容——**机械面文档禁直改** | +| 术语表 | 无新术语(读投影/引用束/公开数据成员均仓库现行词) | 确认行「零新增术语」 | +| game/tests/README.md | 零行为变更=能力清单零新增(判断依据见第 8 节) | 批末核对「表上名字与对照表引用一致」,零编辑 | + +(机检口径:本表同步义务格无一条写「随批」。) + +## 7. 任务卡 T1..T5 + +### 卡前·文件结构图 + +全部行为面=非行为改动(零行为变更批),行标【非行为】+对照表 R 行号;测试件为随逻辑改的测试代码。 + +``` +A1 三件 + game/presentation/views/level_map_screen.gd 【非行为 R1】_MapState 14 字段公开化+87 处改址+:69 UiKit 公开名+头注 + game/presentation/views/battle_end_overlay.gd 【非行为 R1】_EndOverlayState 14 字段公开化+69 处改址+头注 + game/presentation/views/debug_overlay.gd 【非行为 R1】_DebugState 14 字段公开化+50 处改址+头注 +A2 五件 + game/presentation/views/standee_vitals.gd 【非行为 R2】构造/配置交付口+死亡/复位行为方法+读投影(成员 12 零增) + game/presentation/views/standee_motion.gd 【非行为 R2】意图显示/隐藏/复位行为方法+绘制状态读口+挂载口(成员 11 零增) + game/presentation/views/standee_body.gd 【非行为 R2】55 处改址+_hp_flash_tw 归位为宿主成员(10→11)+头注 + game/presentation/views/board_hover.gd 【非行为 R2】+6 公开面(重绑口/复位/刷新/悬停/chord/连接目标/读口) + game/presentation/views/board_view.gd 【非行为 R2】7 处改址(公开方法与成员零净增——贴顶件) +B 七件 + game/presentation/main.gd 【非行为 R3】45 行转发改调公开入口+接线调用点交付 Callable 束+服务公开化+句柄读投影/设置口(例外键申报见第 14 节) + game/presentation/main_ui_build.gd 【非行为 R3】70 处改址(服务/句柄走 main 公开口) + game/presentation/main_adventure_flow.gd 【非行为 R3】入口公开化+29 处改址+接线语句消费 Callable 束 + game/presentation/main_overlay_flow.gd 【非行为 R3】入口公开化+15 处改址 + game/presentation/main_debug_flow.gd 【非行为 R3】入口公开化+11 处改址 + game/presentation/main_esc_flow.gd 【非行为 R3】入口公开化+2 处改址 +C 四件 + game/presentation/views/ui_kit.gd 【非行为 R4】_gradient_base/_beams_rect/_switch_toggle 公开化(体逐字) + game/presentation/views/cell_mark_overlay.gd 【非行为 R4】构造收遮罩下发口(成员 13→14)+2 处改址 + game/presentation/ui_assembler.gd 【非行为 R4】1 处改址 + game/presentation/views/settings_overlay.gd 【非行为 R4】2 处改址 +测试件(随逻辑改) + tests/unit/presentation/presentation_encapsulation_sweep_test.gd 【非行为 R1–R4·新增】四族结构性扫描(非空前置+零命中+正锚) + game/probe 12 件+game/tests 2 件引用替换 【非行为 R3】main._ 交集名字随批替换(当轮真实跑绿) +文档件 + game/tests/README.md 写卡段判断=零编辑(第 8 节判断依据) +``` + +**T1 基线与底账**(估时 0.25 工作日;区间 0.25–0.5) +取用:无。产出:四族 grep 底账+探针替换精确清单+S13 对账基线+零行为变更基线读数。 + +- [ ] 核对处置记录 `reports/处置-presentation层封装修复-2026-11-04.md` 已落盘且与本卡第 3 节逐条一致(冲突=L3 停卡) +- [ ] grep 底账落实现报告(改前账,T5 sweep 的对照面):第 3.1 节同口径 grep 全量命中行清单(14 文件逐文件行号) +- [ ] 探针/测试替换精确清单:`grep -rnE 'main\._[a-zA-Z]' game/probe game/tests` 104 处逐条标注「替换/保留」(判据=名字 ∈ 公开化名单),清单落实现报告 +- [ ] S13 对账基线:第 3.2 节 16 触件现场读数登记;main.gd 例外键实数(规则 3+4 名单逐一数出)核与第 14 节申报区间一致 +- [ ] 零行为变更基线:冒烟 `python tools/run_smoke.py` 全绿读数+渲染 golden 现值(改前即基线)落实现报告 + +**T2 A1 三类字段公开化**(估时 0.5 工作日;区间 0.25–0.75) +取用:T1 底账。产出:A1 族 206 行清零。 + +- [ ] level_map_screen.gd:处置记录 §三 A1 表 14 字段去下划线(体零改动),87 处 `_map._x` → `_map.x`;:69 `UiKit._gradient_base` → `UiKit.gradient_base`(随 T5 公开化先行改址,孤立验证在 T5 对账);头注补一句内部数据类定位 +- [ ] battle_end_overlay.gd:同型 14 字段+69 处;debug_overlay.gd:同型 14 字段+50 处 +- [ ] sweep 测试件骨架与 A1 断言组先行落盘(presentation_encapsulation_sweep_test.gd:三文件非空+class 正锚+`_map\._|st\._` 模式零命中+公开字段正锚+`_MapState|_EndOverlayState|_DebugState` 文件外零消费者断言〔pres封装 V1〕) +- [ ] 孤立验证:sweep A1 组+冒烟——出红只跑红件最小单元 + +**T3 A2 standee 与 board_hover**(估时 1.0 工作日;区间 0.75–1.5) +取用:T1 底账、处置记录 §三 A2 逐字段归属表。产出:A2 族 62 行清零。 + +- [ ] standee_vitals.gd:身份/配置字段(kind/display_name/enemy_id/armor/order/hp_max)收构造与配置交付口(签名按现役调用点数据形态定,成员 12 零增);_dead 归位为 `mark_dead()`/`reset()` 型公开方法;读投影(display_name()/hp_cur()/is_dead() 型)按宿主消费面落 +- [ ] standee_motion.gd:意图行为归位 `show_intent(glyph, col)`/`hide_intent()`/`reset()` 型;绘制状态读口一个(返回 intent 绘制四态,宿主 _draw 消费);portrait 挂载口+读口;_hero_state 生成期走既有 apply_hero_state +- [ ] standee_body.gd:55 处改址(逐字段按归属表);`var _hp_flash_tw` 归位宿主成员(:254-258 逻辑随迁,体逐字);头注「外部直访冻结成员」句按新面改写 +- [ ] board_hover.gd:+6 公开面(rebind(minesweeper)/reset()/refresh_after_paint()/hover_at(pos)/detect_chord(c)/on_shell_mouse_exited 更名公开化/hover_pos() 读口——七处消费逐一对口);board_view.gd 7 处改址 +- [ ] sweep A2 断言组(standee_body `\bst\._|\bmo\._` 零命中+board_view `_hover_comp\._` 零命中+新公开面正锚〔pres封装 V2〕) +- [ ] 孤立验证:sweep A2 组+渲染 golden+战斗探针——出红只跑红件最小单元 + +**T4 B 族正式交接面**(估时 2.0 工作日;区间 1.5–2.5) +取用:T1 探针替换清单、处置记录 §三 B 设计规则六条与 80 名清单。产出:B 族 169 行清零+探针替换跑绿。 + +- [ ] 五分文件入口公开化(main 转发调用的方法去下划线,名表=处置记录 80 名清单分文件侧;体逐字);main.gd 45 行转发改调公开入口 +- [ ] 接线语句改 Callable 束:五个接线入口签名各加 handlers 束参(键=用途名),main 调用点自建束传入(`"adventure_finished": _on_adventure_finished` 型,main 内自引);分文件 connect 语句消费束值——`root._on_xxx` 引用清零 +- [ ] main.gd 服务方法公开化(处置记录 §三 B 规则 3 名单,体逐字);句柄成员补公开读投影/设置方法对(规则 4 名单,方法体一行转发,成员零增) +- [ ] 五分文件 124 处 root._ 改址(服务走公开方法、句柄走读投影/设置口、处理器经束) +- [ ] 探针/测试替换:按 T1 精确清单逐条替换(引用形态迁移,断言内容零改动) +- [ ] main.gd 例外键实数核 对与第 14 节申报一致;S13 键对账 +- [ ] sweep B 断言组(六文件 `root\._|main\._|MainOverlayFlow\._|MainAdventureFlow\._|MainDebugFlow\._|MainEscFlow\._` 零命中+新公开面正锚〔pres封装 V3〕) +- [ ] 孤立验证:sweep B 组+探针替换件当轮真实跑+冒烟+系统测试——出红只跑红件最小单元 + +**T5 C 族+批收口**(估时 0.375 工作日;区间 0.25–0.5) +取用:全部前卡。产出:C 族清零+结构性恒零证据+批收口两件。 + +- [ ] ui_kit.gd 三工厂公开化(体逐字);cell_mark_overlay 构造收遮罩下发口(board_view 构造点随改,board_view 面零净增)+2 处改址;ui_assembler/settings_overlay 改址;level_map_screen :69 改址对账(T2 已预改) +- [ ] sweep C 断言组(ui_kit 私有工厂全库零外调+cell_mark `_owner\._` 零命中+正锚〔pres封装 V4〕);run_all.gd manifest 追加 1 行(sweep,expect 0) +- [ ] 全量 sweep 复核+冒烟+渲染 golden+探针套件终读数(零行为变更证据链闭合) +- [ ] **实现报告** `reports/iter-实现报告-<日期>.md`:各卡状态+S13 键改前改后对账(T1 基线逐件核+main.gd 例外键落基线记录)+探针替换清单执行账+实现决定记录 +- [ ] **反哺点清单** `reports/iter-反哺点-<日期>.md`:第 6 节同步面逐条落点(architecture.md 呈现层各行登记内容、术语确认行) +- [ ] 批末核对:game/tests/README.md 零编辑核对;独立验收审计(第 8 节末)未过不合并;.uid 收口=`--editor --quit` 一次后确认新测试件 .uid 随批提交 + +### 估时汇总 + +T1 0.25|T2 0.5|T3 1.0|T4 2.0|T5 0.375。区间合计 3.5–5.25 工作日;**精确汇总 4.125 工作日**(切分预案:若 T4 超 2.5 工日,把 T5 切出为独立半日卡顺延,其余不动)。 + +## 8. 验证闸(改动—验收对照表) + +测试方式两分法:下表全部走**调试接口验证**(sweep 源扫描+既有套件+探针真实跑);**正常流程体验验证**=零项(零行为变更批,实机观感逐位同改前)。判据绑被测改动特有的可观测提交面(源码私取模式零命中带非空前置与正锚/探针可见性与信号面零漂移)。 + +| 需求点 | 改动(文件:行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---| +| R1 A1·战末弹层 | battle_end_overlay 14 字段+69 处改址 | sweep A1 组〔pres封装 V1〕;冒烟+战末弹层既有探针回归 | 探针 | `BattleEndOverlay.open_victory()`(战末弹层真实开合链) | 私取模式零命中+公开字段正锚+文件外零消费者;弹层可见性与载荷零漂移 | 既有:站桩敌人呈现与结算演出落点 | +| R2 A1·调试浮窗 | debug_overlay 14 字段+50 处改址 | sweep A1 组〔pres封装 V1〕;调参相关探针既有回归 | 探针 | `TuneSimController.run()`(调试浮窗批量模拟入口) | 同 R1 判据面;浮窗呈现与批量模拟读数零漂移 | 既有:调参测速批量模拟(逐帧步进与统计读数) | +| R3 A1·关卡画面 | level_map_screen 14 字段+87 处+:69 改址 | sweep A1 组〔pres封装 V1〕;冒烟+选路集成既有回归 | 集成+探针 | 选路集成链(起关→选路→断点重生成)+冒烟全链 | 私取模式零命中+公开字段正锚;选路链零漂移 | 既有:起关/选路/断点重生成同图(集成) | +| R4 A2·站桩三件 | standee 三件改址+_hp_flash_tw 归位 | sweep A2 组〔pres封装 V2〕;渲染 golden 逐像素零转红;站桩探针既有回归 | 探针+golden | `BattleStageView` 真实实例化(站桩渲染链) | 站桩呈现逐位同;子件私取模式零命中+新公开面正锚 | 既有:站桩敌人呈现与结算演出落点 | +| R5 A2·悬停子件 | board_hover 六公开面+board_view 7 处改址 | sweep A2 组〔pres封装 V2〕;ui_input_probe 既有回归 | 探针 | `ui_input_probe` 合成鼠标事件→`BoardView._gui_input()` | 悬停三语义零漂移;子件私取零命中+六公开面正锚 | 既有:棋盘悬停反馈(三语义光标/目标格边框/路径预览点迹) | +| R6 B·组合根交接面 | main+五分文件+探针/测试替换 | sweep B 组〔pres封装 V3〕;14 件替换后当轮真实跑绿;冒烟+系统测试 | 集成+探针 | `ui_click_probe` 大门两分支段(main 处理器→分文件→营地态全链)+探针套件其余件 | 六文件私取模式零命中+新公开面正锚;探针断言零删改引用改址后全绿 | 既有:营地大门两分支确认与断点续玩接线 | +| R7 C·杂项公开面 | ui_kit 三工厂+cell_mark 构造交付+改址 | sweep C 组〔pres封装 V4〕;layout/shot 探针既有回归 | 单元+探针 | 界面装配真实链(ui_kit 工厂消费面) | 私有工厂零外调+cell_mark 私取零命中+呈现零漂移 | 既有:站桩呈现定值与素材绑定 | + +**零行为改动面的证据义务**:本卡全部行为面为非行为改动(零行为变更批)——不设「行为改动」行;每行零变更证明=渲染 golden 零转红+探针套件零转红+冒烟/单元零转红+sweep 恒零。能力行全部「既有」、零新增——判断依据:玩家可观测能力面零变化,无新能力可登记,`game/tests/README.md` 零编辑(批末核对义务在 T5)。 + +**闸口径**:重闸=`tools/run_all_gates.sh` 按本批改动域选档(改动含 game/ 下非 .md 行为载体 → `--code`);所选域全绿才合并。卡内不写死闸件名单。回归措辞:里程碑卡(T 段)出口跑一次全套闸;卡内修复只做出红最小单元的最窄确认;新增测试先行孤立验证再并入全套。 + +**测试削弱申报**(允许改/删的旧断言全清单;未申报的删改由独立验收审计判红): +1. 探针/测试 14 件中与公开化名字交集的 `main._x` 引用 → 改公开名(清单=T1 精确清单)——随被测改动同步的引用形态迁移,断言内容零改动,无一条删除断言或放宽阈值。 + +**独立验收审计**:入口=批末 T5 后、合并前;报告=`reports/iter-验收审计-<日期>.md`;由没参与实现的会话或子代理执行,核对:对照表逐行断言真实存在且当轮跑批通过、零行为变更证据链在案(golden/探针/冒烟改前改后零转红读数)、测试削弱申报 1 条之外零删改、第 13 节无效成员零残留、S13 键对账(含 main.gd 例外键申报一致)。未过不合并。 + +## 9. 异常分级 + +- **L1 自救**:改址遗漏一处编译级报错、字段名笔误、注释错字——就地改,记录进实现报告。 +- **L2 记录继续**:行号 ±5 行内漂移(grep 符号锚定位后继续);探针失败经排查属既有缺陷且与本批无因果——登记跳过并报用户。 +- **L3 停机写报告等用户**:①卡面与处置记录冲突、或处置记录未落盘/与本卡第 3 节不一致(上游缺陷,退写卡段);②零行为变更契约被破(golden/探针/套件任何非预期漂移=停卡,禁就地调和);③S13 预算互斥——任一触件实测键超第 14 节预期(含 main.gd 实数超申报区间、board_view/standee_body 贴顶件需增面)=上游缺陷停卡退写卡段,禁实现段自拆或砍面;④实现中发现处置记录 80 名清单之外的第 81 个私取名字或未登记消费面(语义不明,禁代裁);⑤机检键与卡片契约互斥=停卡呈报,不得自造第三条路。 + +## 10. 代拍板白名单 + +- 测试内部夹具命名与组织(sweep 正则具体写法、断言消息措辞——判据面不变,尾缀〔pres封装 Vn〕保留)。 +- 注释与头注改写措辞(模块介绍职责不变)。 +- A2 读投影/行为方法的命名风格(`xxx()` 读投影与 `apply_xxx` 动词族内一致化——签名面按第 7 节 T3 定死的职责句,不改归属)。 +- Callable 束的键名(用途名)与断言/报错文案。 +- 逐卡内「孤立验证→出红最小单元→并入全套」的执行顺序细节。 +超清单的决断记入实现报告「实现决定记录」节。 + +## 11. 待裁点 + +**零待裁点,开工门=U0**——四族修法、三个内部数据类定位(改法A)、一批做完(方案一)、B 族设计规则六条、main.gd 指标例外申报方向均已在 2026-11-04 设计讨论会话由用户裁决;字段/名字清单、方法签名、T 卡分解、验收断言由本卡写卡期实查定死(第 3/7 节)。本卡未新增任何备选案。 + +## 12. 落位与可复用性预检 + +**新增文件(1 件,测试件)**: +- tests/unit/presentation/presentation_encapsulation_sweep_test.gd——为什么不能复用 core 的 encapsulation_sweep_test:四族结构性零命中是本批特有扫描面(A1 三类+A2 子件+B 六文件+C 杂项,与 5.1 卡的 core 七文件集完全不同面);player_copy_sweep_test 扫 presentation 文案面——**复用其源 token 扫描手法但不并件**(并件让两批扫描面与尾缀语义混乱——5.1 卡 :257 同款论证)。落点 tests/unit/presentation(被扫对象全在 presentation);grep 同名零命中。 + +**新增函数/成员(逐条唯一性核查义务,grep 同名全库实核)**: +- A1:仅字段改名公开化,零新增函数/成员(grep 确认公开名与既有成员名无冲突)。 +- A2:standee_vitals/motion 新公开方法(构造交付口/行为方法/读投影)——grep 方法名全库零第二处;命名与既有动词族同型(apply_hero_state/mo 现役方法族先例);board_hover 六公开面 grep 唯一;`_hp_flash_tw` 归位 standee_body(成员迁移非新增语义)。 +- B:main.gd 服务公开化为既有方法去下划线(禁别名);句柄读投影/设置方法对命名 grep 零同名(BreakpointStore set_/get 同族先例);Callable 束键名 grep 零冲突。 +- C:ui_kit 三方法去下划线公开化(体逐字,禁留旧名);cell_mark 构造参数(成员 13→14)。 + +**复用既有(不新增)**:Callable 束交付形态=BoardInstaller/5.1 卡束先例;读投影形态=has_write_target/active_path 先例;句柄方法对=BreakpointStore 13 方法先例;源扫描测试=player_copy_sweep_test 手法;manifest 登记=run_all.gd 既有条目族。零新增生产文件(全部落既有件)。 + +**分层与交互面**:本批零跨层新增路径——presentation 内部往来改公开面,对 core/data 的调用面零触碰(settle_batch 等组合根写口现役形态不变);Core 零 UI 红线零触碰;数值单一事实源零触碰;RngService 红线零触碰(零新增随机源)。 + +## 13. 禁为机检文本保留无效成员 + +本卡为重构类卡,全卡适用(code-conventions §1.1 ⑤同款):禁止为迎合机检消息文本(含计数、sweep 正则、grep 面)保留或新增无效成员/别名/转发壳;A1 字段公开化后禁留私有旧名;B 族去下划线后禁留旧私有名;main 侧既有单行转发层为登记的探针契约承载(非无效成员,反哺点登记),除此之外禁新立转发层;sweep 正则与签名冲突时停卡呈报,不得自造第三条路。 + +## 14. 实现规范预算 + +新增 .gd 仅 1 个测试件(SceneTree 测试脚本,全类型标注、零公开生产面)。生产代码全落既有件,按「存量触面一查」对账(S13 默认阈值:公开方法 ≤20/类变量 ≤15/分支 ≤8/返回 ≤6): + +- **指标例外申报(main.gd)**:键=`game/presentation/main.gd|quality/max-public-methods`,现值 1,预期 25–30(T1 实数核);理由=组合根服务面(分文件消费的公开口集中于此),架构先例=level_runner.gd 公开方法 33 在册基线、adventure_runner.gd 类变量 23 在册基线;落法=实现批 `python tools/gdstyle_gate.py --refresh`(人工专用通道,随批重生成)。 +- **贴顶件约束**:board_view 27/31(基线在册)**零净增**(本批对 board_view 的全部动作=7 处改址+构造点传参);standee_body 公开方法 20 贴顶零增(成员 10→11 仅 _hp_flash_tw 归位);debug_overlay 公开方法 19——A1 只改字段名不动方法,零变化。 +- **类变量净增件**:cell_mark_overlay 13→14(构造交付的遮罩口引用);其余触件零增(A1 改名不增数;vitals/motion 成员零增)。 +- **公开方法净增件**:五分文件 0→3–15;board_hover 0→6;standee_vitals 1→预算内、standee_motion 6→预算内(均 ≤20);ui_kit 0→3;main.gd 见例外申报。 +- **迁移函数对账**:本批公开化方法全部「体逐字去下划线」,无跨文件迁移体;唯一迁移=_hp_flash_tw 归位(standee_body 内自用逻辑随迁,非跨件函数迁移);若 T1 读数发现源函数 branches/returns 超默认登记值,随迁即拆函数(拆分不改语义,与第 13 节区分)。 +- **存量触面 S13 预期**:16 触件 S13 键较 T1 现场基线**只降不升**(board_view 两基线键持平为达标);降不达=检查是否夹带超预算面,L3 ③兜底。 + +## 15. 拆分边界纪律 + +见第 4 节红线(判据权威=code-conventions §1.1):本批**零新拆件**——全部落架构在册既有件(组合根 main、五分文件、三数据类、standee 三件、board_hover/board_view、ui_kit、cell_mark),只做往来形态换代。逐件职责语句不变:A1 三类=宿主画面文件的登记表(数据类定位,用户裁);A2 子件=各自职责域件(身份命值态/资产演出态/悬停呈现域件——architecture.md 在册),行为归位=误放宿主的操作回归所有者;B 族=组合根委托层公开契约化(接线所有权不动);C 族=公用设施身份归位。单一变化原因=封装边界(跨对象私取清零),非阈值驱动。禁凑计数袋。 diff --git a/待办清单.md b/待办清单.md index bcc2bd97..2f0424ed 100644 --- a/待办清单.md +++ b/待办清单.md @@ -12,13 +12,11 @@ #### 架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移(2026-11-03 登记,来源=批次移交) -**实况**:2026-10-29 全库架构审计(reports/审计-全库架构审查-2026-10-29.md;3.1+3.2 已随迭代 50 合回,凭据 reports/处置-架构审查3.1+3.2整改-2026-11-02.md)的待裁余项,自同名讨论条目迁入。现状(2026-11-04 对账):①闸面缺口(扫描面不扩 sim/probe/tests,适用域落 code-conventions §4 第 13 条)②S13 豁免存废(豁免已清偿,基线 5 键,commit 9eedf896)③零测试点名(定性=无按文件登记惯例,承接面落 test-conventions §8.3「新增 core 件的覆盖登记义务」)三组已随 §4 批裁决并落地(2026-11-03 用户逐项点头,CI 66407117 green);④架构文档漂移三处已随 arch51-docs 批落文档合回;§5.1 封装修复已写卡(reports/需求卡片-架构审查5.1封装修复批-2026-11-03.md v1.1,5 张 T 卡 2.875 工作日,凭据含 reports/处置-架构审查5.1封装修复-2026-11-03.md,排序在 3.1+3.2 卡后);§5.3 已写卡(reports/需求卡片-架构审查P2代码面批-2026-11-03.md v1.1,0.25 工作日,排序在 5.1 卡后);EC8 热重载侧经用户裁撤卡(实装去向=用户另立条目)。 - -**缺口**:还差 presentation 层私取面(约 227 行)的设计讨论——本条最后余项;另 §5.1/§5.3 两卡待编迭代。 +**实况**:2026-10-29 全库架构审计(reports/审计-全库架构审查-2026-10-29.md)的待裁余项,自同名讨论条目迁入。现行:①闸面缺口(适用域落 code-conventions §4 第 13 条)②S13 豁免清偿(commit 9eedf896)③零测试点名(承接面落 test-conventions §8.3)三组随 §4 批落地(2026-11-03 用户裁决,CI 66407117 green);④架构文档漂移随 arch51-docs 批合回;§5.1 卡=reports/需求卡片-架构审查5.1封装修复批-2026-11-03.md v1.1(2.875 工作日);§5.3 卡=reports/需求卡片-架构审查P2代码面批-2026-11-03.md v1.1(0.25 工作日);EC8 热重载经用户裁撤卡(实装去向=另立条目);presentation 层私取面 2026-11-04 设计闭合并写卡(443 行/14 文件;用户两裁=三内部数据类字段公开化+四族一批)=reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md v1.0(4.125 工作日,凭据=reports/处置-presentation层封装修复-2026-11-04.md)。 **改动面**:文档+游戏代码+工具代码 -**状态**:文档设计中+已写卡+已编迭代 51 +**状态**:文档设计已闭合+已写卡+已编迭代 51 #### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(2026-10-28 登记,来源=会话提及) From 1cd6e2b1b09d537685f191e894f840c84db70277 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:08:31 +0800 Subject: [PATCH 02/19] =?UTF-8?q?iter51-=E5=8D=A11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/art/art-assets-checklist.md | 4 +-- design/art/art-bible.md | 29 ++++++++-------- design/art/art-status-checklist.md | 4 +-- design/gdd/02-系统清单.md | 2 +- design/gdd/08-Alpha版本标准.md | 6 ++-- design/gdd/systems/战斗循环/战斗表现.md | 4 +-- design/gdd/ux/battle.md | 2 +- game/assets/art/board/seal_mark.png | Bin 223 -> 123 bytes game/core/action_economy.gd | 31 +++++------------- game/core/battle_fx_host.gd | 14 +++----- game/core/battle_orchestrator.gd | 2 +- game/core/damage_controller.gd | 9 +++-- game/core/effect_engine.gd | 10 +++--- game/core/effect_port.gd | 4 +-- game/core/settlement/combat_segment.gd | 4 +-- game/core/settlement/grant_segment.gd | 6 ++-- game/tests/run_all.gd | 2 +- .../battle_settlement/effect_port_test.gd | 12 ++++--- game/tests/unit/core/treasure_effects_test.gd | 6 ++-- 体验验证清单.md | 2 -- 待办清单.md | 8 ++--- 21 files changed, 68 insertions(+), 93 deletions(-) diff --git a/design/art/art-assets-checklist.md b/design/art/art-assets-checklist.md index 1924aad5..8a1d1026 100644 --- a/design/art/art-assets-checklist.md +++ b/design/art/art-assets-checklist.md @@ -117,8 +117,6 @@ ### 3.3 雷 / 封印印记 / 数字格面像素化 ⚙ -- **封印印记已换代**:形 = 环+十字法阵、纯黑 32×32,形与定值见 art-bible §3.2 与 §8.2「封印印记源档」;本节余下面 = 雷格纹样与数字格面。 - - **要什么**:格子样张通过后按冻结参数出全套砖面资产(含图类型换色板组)。 - **来源**:⚙ 脚本管线自产——未开/已开/危险格/变异纹带是扫雷专属语义(角嵌纹样已不存在),素材站没有对的;砖面**纹理底噪**可 ♻ 用 CC0 无缝石/土纹理作脚本管线的纹理输入,量化到色板(混合路线)。 - **前置**:§1.1 格子样张通过(**未跑**)。 @@ -164,7 +162,7 @@ | 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint;连续地面层在其下方,先保留 | ⚙/✦ 自产 | | 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | ✦/⚙ 自产(同 §2.3) | | 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | ♻ 粒子/碎块改色 + ⚙ 坍塌结构自产 | -| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记常显形态**已落位**——`seal_mark.png`(32×32 纯黑,环+十字法阵;形与定值见 art-bible §3.2/§8.2);施印瞬间特效**未实装**,仍缺(走运动+色相,明度分层不动) | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | +| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记旗面走封印黑 `#000000`(现行封印色,色史归 art-bible §4.1);特效走运动+色相(现行取法——明度分层不动);现行占位 = 封印黑「旗」字形;印记常显形态可与 §3.3 旗面像素化合并出 | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | | 弹道/退场等动画密度 | 战斗表现输入清单,方案未定 | 样张锁动画方案后启动 | ♻ 通用弹道/光点/消散粒子改色为主 | **现状**:三关舞台背景层与连续地面层已交付(2026-10-31 入包 `game/assets/art/scene/`:dungeon v10c / underdark v10c / deep_underdark v10c,均 1280×252;共用地面 v12b,1280×100,按作者裁保留原亮度 L≈0.132);小石台件已在役。 diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 0e1ba0e4..b1301483 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -1,6 +1,6 @@ # 美术圣经(Art Bible)— Dungeon Sweeper -> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.40,历次修订明细见 git 提交史 +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.37,历次修订明细见 git 提交史 > > **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。 > 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。 @@ -22,7 +22,7 @@ **原则一|预告上格**(取自 Into the Breach;锚定 P1 推理的掌控感、P2 规划的延迟兑现) -继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格内绘制优先级固定:**数字(含色牌) > 小人标记 > 封印印记 > 变异纹带 > 装饰变体 > 底色**,任何元素禁止遮挡数字。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡数字〉 +继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格内绘制优先级固定:**数字(含色牌) > 小人标记 > 旗 > 变异纹带 > 装饰变体 > 底色**,任何元素禁止遮挡数字。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡数字〉 *范围注:本原则约束"已存在的信息往哪里画",不新增信息种类;预告类信息以战斗侧 GDD 实际定义的为准(如 P2 已锁的结算来源标注),GDD 未定义的预告不因本条而存在。* @@ -38,7 +38,7 @@ 格子即地牢地砖:未开 = 覆尘完整砖,已开 = 碎裂裸露,雷 = 被惊动的巢穴(格态定义与玩家说法见《排雷玩法》),图类型(board_type)= 同款地砖换色板。图类型与变异格的视觉差异一律先做色板变体,不做新资产;一切危险状态必须"形状 + 颜色"双通道冗余,色弱可辨。像 Moonlighter 之处在砖缝质感与暖色道具,但装饰禁止伪装成逻辑差异——玩家须一眼分清"好看"与"有雷"。验收:目检〈危险状态均为形状+颜色双通道,色弱模拟下可辨〉 -格子同时是**内容舞台**,格与格的美术表达按格内实态分层多样化(画法细则 §3.2)。已开格按当格结果整格表达——产资源格嵌对应家族物件纹样(地图战斗资源九类型,§4.6.3)、纯安全格呈现干净裸土、危险格巢穴更凶。未开格在覆尘完整砖之上叠加**装饰变体**(裂纹/碎石斑/物件级点缀等氛围层),用途是破除大面积同砖平铺的重复感。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**:形与信息元素(数字/小人/封印印记/纹样)形状语言隔离,灰度下不与任何逻辑元素同形。零信息纪律:随机分布、与雷/安/类型零相关、禁止构成可读线索、分布每板一次跨轮不复掷。焦点预算约束:装饰不构成完整凸形、不抢读(§3.4);数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4)。未开格的类型预读画为**类型微光单通道**(色相+明度双码;仍只表达类型,不泄危险格与安全格——呈现契约见 §4.6.3;概念定义、可见范围与生命周期见《HUD框架》「未开格类型微光」节)。验收:目检〈物件级装饰与逻辑元素形状隔离;装饰不构成完整凸形〉 +格子同时是**内容舞台**,格与格的美术表达按格内实态分层多样化(画法细则 §3.2)。已开格按当格结果整格表达——产资源格嵌对应家族物件纹样(地图战斗资源九类型,§4.6.3)、纯安全格呈现干净裸土、危险格巢穴更凶。未开格在覆尘完整砖之上叠加**装饰变体**(裂纹/碎石斑/物件级点缀等氛围层),用途是破除大面积同砖平铺的重复感。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**:形与信息元素(数字/小人/旗/纹样)形状语言隔离,灰度下不与任何逻辑元素同形。零信息纪律:随机分布、与雷/安/类型零相关、禁止构成可读线索、分布每板一次跨轮不复掷。焦点预算约束:装饰不构成完整凸形、不抢读(§3.4);数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4)。未开格的类型预读画为**类型微光单通道**(色相+明度双码;仍只表达类型,不泄危险格与安全格——呈现契约见 §4.6.3;概念定义、可见范围与生命周期见《HUD框架》「未开格类型微光」节)。验收:目检〈物件级装饰与逻辑元素形状隔离;装饰不构成完整凸形〉 *设计测试:当新图类型或新变异格需要新视觉时,先换色板加形状,再谈新资产;当格子多样化与"好看/有雷"之辨冲突时,砍装饰,不动逻辑通道。* @@ -46,12 +46,12 @@ > 立意:扫雷数字可读性与暗色氛围相冲突,由一次格子样张测试定风格,具体形态由样张测试定稿。**样张** = 一张按本圣经参数画成的格子风格试验图,是砖面形态的定稿闸——样张通过前,§3.2/§4.1 的一切参数只是候选,通过后冻结为制作标准。 -**样张内容清单**(一图齐备,缺项即无效):未开格(覆尘完整砖,含至少两种装饰变体)、已开格(碎裂裸露,含 0 格与 1–8 全档数字)、危险格(被惊动的巢穴)、封印印记、小人两态(独立占格 + 与数字同格——后者须验"边框高亮不覆盖文本")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(已开格底亮墨直画组 + 复杂格底色牌后备组——素底/装饰变体底/变异纹带底至少三种格底上各一组数字)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**类型微光呈现组**(至少三个类型的微光在纯砖格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉 +**样张内容清单**(一图齐备,缺项即无效):未开格(覆尘完整砖,含至少两种装饰变体)、已开格(碎裂裸露,含 0 格与 1–8 全档数字)、危险格(被惊动的巢穴)、旗、小人两态(独立占格 + 与数字同格——后者须验"边框高亮不覆盖文本")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(已开格底亮墨直画组 + 复杂格底色牌后备组——素底/装饰变体底/变异纹带底至少三种格底上各一组数字)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**类型微光呈现组**(至少三个类型的微光在纯砖格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉 **验收项按序判定,第一项不过 = 整张否决(数字可读性定风格,风格服从可读)**: 1. **第一验收项|数字可读性(硬指标,可测)**:1–8 全档数字对其所在**已开格底**直画 WCAG 对比 ≥4.5:1(参数域见 §4.1:已开格底走世界底层档、棋盘数字墨亮族);复杂格底(物件纹样/变异纹带/微光叠加致直画对比不足)启用**色牌后备**,牌墨组合 ≥4.5:1(色牌与牌上深墨族数字的定值见 §4.1),牌缘灰度可分;0.5× 缩放抽检数字仍可辨;纹带/裂纹/巢穴纹/物件纹样禁止侵入数字字形占位区(砖心留白,§3.2)。验收:机检〈1–8 数字对已开格底直画与牌墨组合对比均 ≥4.5:1〉 -2. **格态三形可辨(灰度通道)**:未开/已开/雷三态转灰度截图后仍可逐格分类;明度差(墙亮/空间暗)与碎裂形同为合法分类通道,双通道冗余(§3.2 完整度叙事)。装饰变体禁止制造新的分类特征——同一格态下带与不带变体的灰度差异禁止成为三态分类线索。色弱模拟(去色相)下封印印记、巢穴纹不与裂纹混淆。**五组类型微光互辨**:色弱玩家的类型主通道 = 微光,色相+明度双码,微光以明度梯度承载分组(组内不细分,属 §2 信息面层内梯度);微光禁止侵入数字字形占位区,明度梯度禁止压破格底明度档(三态分类不受扰)。验收:目检〈三态灰度逐格可分类;五组微光互辨;色弱模拟下封印印记、巢穴纹不与裂纹混淆〉 +2. **格态三形可辨(灰度通道)**:未开/已开/雷三态转灰度截图后仍可逐格分类;明度差(墙亮/空间暗)与碎裂形同为合法分类通道,双通道冗余(§3.2 完整度叙事)。装饰变体禁止制造新的分类特征——同一格态下带与不带变体的灰度差异禁止成为三态分类线索。色弱模拟(去色相)下旗、巢穴纹不与裂纹混淆。**五组类型微光互辨**:色弱玩家的类型主通道 = 微光,色相+明度双码,微光以明度梯度承载分组(组内不细分,属 §2 信息面层内梯度);微光禁止侵入数字字形占位区,明度梯度禁止压破格底明度档(三态分类不受扰)。验收:目检〈三态灰度逐格可分类;五组微光互辨;色弱模拟下旗、巢穴纹不与裂纹混淆〉 3. **焦点预算成立(§3.4)**:任意一格至多一个完整凸形;数字所在格无第二焦点。验收:目检〈任一格至多一个完整凸形,数字所在格无第二焦点〉 4. **图类型换皮抽测(§4.3)**:变体组中数字墨亮族三档、封印黑、危险红、奖励金、小人蓝、数字色牌(后备件)、类型微光五组恒定不动;变体格底仍过第 1、2 项指标。验收:机检〈换皮后不变槽色值恒定,变体格底仍过第 1、2 项〉 @@ -59,7 +59,7 @@ **整屏样张闸**:格子样张验"格",整屏样张验"屏"——战斗屏/关卡屏/菜单屏/营地屏各出一张整屏样张,验收四项:①全部文字(含菜单/说明/tooltip/按钮)对其直接背景 ≥4.5:1(WCAG AA),大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;②明度分层三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4:首焦唯一——数字区/立绘/主按钮,无第二焦点竞争;负空间服务焦点,不属空窗);④文字禁直接压纹理底(垫容器或罩层)。验收顺序 = **方向稿目检先行**(防"机检过闸但气质否决"式返工),目检过后机检冻结。执行时机 = 屏级视觉落码开工前;过闸前一切屏级视觉为占位临时态(§7.1 完成条款)。**样张判定物的产出手段与素材输入级别不进圣经定值**——圣经只定样张要验什么,不定样张以何种手段产出、也不定它取哪一级别素材输入。验收:机检〈整屏样张四项断言全绿,且目检先行过审〉 -**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸(方向稿目检已通过,凭据=体验验证清单 ㊶) +**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸待方向稿目检先行(真人检测,登记于体验验证清单 ㊶) **样张现状**:格子样张已冻结,冻结凭据 = git 提交 7a847c7(机检报告)与 §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 亮墨三档直画 + 色牌后备(值见 §4.1);新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉 @@ -164,7 +164,7 @@ - **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三 - **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3 - **未开格预读通道(微光单通道)**:类型预读 = 类型微光单通道(色相+明度双码,色带 §4.2);色弱类型主通道 = 微光明度梯度(组粒度——组间互辨;§2 微光明度例外);格内零角嵌/箭头/加号类附加形(微光格只留微光)。验收:目检〈微光格内零角嵌/箭头/加号类附加形〉 ←原一;原三 -- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法纪律**:纯黑实心剪影,禁细线描边、禁符文字形——32px 下细线会断成碎点,阵纹一律做成实心块面。**尺寸**见 §8.2「封印印记源档」条(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),无发光、无描边、无渐变。**占位**:印记居格心,最外沿不越格缘空带(见 §8.2);封印印记只出现在未开格,不与数字同格。格内栈位见 §1.2。封印特效(施印瞬间 + 印记常显形态)资产归清单 §4 演出类——常显形态即本件,施印瞬间特效未实装。封印印记纹理已落位(现行件 = `seal_mark.png`)。验收:目检〈印记为环+十字、四端与环相接;无发光与描边;最外沿不越格缘空带〉 ←原一;P2 +- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记;封印色见 §4.1(现行封印黑),格内优先级栈位见 §1.2。封印特效(施印瞬间 + 印记常显形态)资产归清单 §4 演出类;封印印记纹理已落位(现行件 = `seal_mark.png`,32×32 可选件),数字字形占位已不存在。验收:目检〈封印印记呈现与封印黑一致,不侵入数字字形占位区〉 ←原一;P2 - **可挖域接触带微光(交互反馈层)**:挖掘期,英雄格周界与可挖域(概念见《排雷玩法》动作合法性节;呈现义务见《HUD框架》可挖域指示)的接触面画呼吸微光——正交邻墙 = 该侧整条边,斜向邻墙 = 共享角点 + 向两侧各延伸半条边;光只画英雄格(空间)一侧、不进墙格(与装饰变体零冲突,数字砖心不涉);微光 = 空间的灯光边界(画法规则)。色相 = 蓝族浅一档(§4.2 蓝条;与主角蓝框的分层由样张测试定,§1.4);呼吸 = §2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认 = 悬停金框(呈现交互归《HUD框架》悬停指示域)。验收:目检〈微光只画英雄格一侧接触面:正交邻墙整条边、斜向邻墙角点 + 半边〉 ←§2.1;§4.2;P1 - **墙体**:圆角砌块堆叠,禁尖刺装饰——尖刺专属危险语义,装饰禁止伪装逻辑差异。验收:目检〈墙体无尖刺装饰〉 ←原三 - **高墙瓦片体系|术语与定义**:未挖格 = **一整块 32×32 自含凸起岩体**;素材语义名——未开格素材 = **墙瓦片**,已开格素材 = **地面瓦片**。瓦片以自含岩体为单位,Tiny Dungeon 拼块套不进自含岩体定义,不作瓦片参考。验收:目检〈未开格为整块凸起岩体,无拼块接缝感〉 ←原三 @@ -173,7 +173,7 @@ - **高墙瓦片体系|凹角收头**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出。验收:目检〈凹角收头与转角竖线同列,无悬空突出〉 - **高墙瓦片体系|现行定稿**:`game/assets/art/board/terrain_*`(16 唯一墙瓦片 + 5 地面瓦片变体;词表与查表逻辑落码于 `board_world.gd`)。验收:机检〈terrain_* 素材 16 墙瓦片 + 5 地面变体在库,查表逻辑与词表一致〉 - **高墙瓦片体系|高墙感 D 投影档与整板悬崖**:南临空墙格 = 立面口沿高光 2px `#C9AF83`(201,175,131——代码色 `BRICK`,与掩尘枚同源单点) + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起);整板悬崖 = 板底/右侧 `DEPTH_PX 8px` 暗面 + 列分隔竖缝 + 岩层横纹 + 口沿高光 + 接触 AO + 底线收边 + 悬浮影(游戏 `board_world.gd` 同款结构件,样张脚本与游戏渲染常量同源)。验收:目检〈南临空墙格呈口沿高光+双段暗带;北邻墙地面格呈三段渐隐投影;整板悬崖结构件齐备〉 -- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与封印印记;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/封印印记);原三 +- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/旗);原三 ### 3.3 UI 形状语法(方向指导,细则见 §7.1) @@ -181,7 +181,7 @@ ### 3.4 主角形状与对比预算 -显著度 = 完整度:**每格至多一个"完整凸形"**(数字/小人/封印印记,依 1.2 优先级让位;装饰变体贴砖缘分布、不构成第二完整凸形——§4.6;数字色牌随数字一体,不作独立焦点;类型微光为底效泛光,不作焦点、不抢完整凸形预算),底砖与装饰一律残缺、内凹、低对比而自然后退;危险用刺与缺(排斥形),收益用满与圆(吸引形),同表预算、此消彼长。焦点分配双机制:**完整度**(形状,本条上文)+ **明度带**(§2 明度分层——信息面层恒亮于世界层,焦点天然落信息面);明暗只做层间分离,层内禁明暗竞争(不靠明暗抢焦)。验收:目检〈任一格至多一个完整凸形;信息面层恒亮于世界层〉 ←原二;P1;P3 +显著度 = 完整度:**每格至多一个"完整凸形"**(数字/小人/旗,依 1.2 优先级让位;装饰变体贴砖缘分布、不构成第二完整凸形——§4.6;数字色牌随数字一体,不作独立焦点;类型微光为底效泛光,不作焦点、不抢完整凸形预算),底砖与装饰一律残缺、内凹、低对比而自然后退;危险用刺与缺(排斥形),收益用满与圆(吸引形),同表预算、此消彼长。焦点分配双机制:**完整度**(形状,本条上文)+ **明度带**(§2 明度分层——信息面层恒亮于世界层,焦点天然落信息面);明暗只做层间分离,层内禁明暗竞争(不靠明暗抢焦)。验收:目检〈任一格至多一个完整凸形;信息面层恒亮于世界层〉 ←原二;P1;P3 --- @@ -199,7 +199,7 @@ - **棋盘数字呈现 = 按板自适应三态**:暗纹理板(dungeon 系)= **亮族**三档 1–3 冷亮青白 #D8ECF0/4–5 暖白 #F0E2C8/6–8 金亮 #F8E3A8 直画(判据 = 族最弱成员对 p90 ≥4.5:1 且对均值 ≥2.5:1);双峰纹理板(underdark/deep_underdark 系)= **色牌垫底**(素牌 DIGIT_PLATE + 深墨 #33261B 12:1,纹理免疫——双峰纹理板上任何墨族直画都不可读,渲染帧取证;判据 = 亮深两族皆不过门,深族均值门 3.0);缺盘平色回退态(#B39A78 中明度底)默认深墨直画。选模式管线落码单点 `board_view._digit_ink_mode`(三态:亮直/深直/落牌),机检面 = `terrain_tile_sets_test` 三关呈现模式断言;目视验证面 = board_shot_probe 渲染帧取证(机检≠看得清)。验收:机检〈terrain_tile_sets_test 三关呈现模式断言全绿,board_shot_probe 渲染帧目视复核〉 - **垫牌与 UI 数据墨 = 深墨族 `#33261B` 系**(L≤0.027,§7.2)。验收:机检〈垫牌与 UI 数据墨取深墨族,L≤0.027〉 - **八色弃用**:八色必与红/金/蓝/紫赤语义冲突,故弃。验收:方向性 -- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,纯黑实心剪影单靠明度差即成立,不依赖任何浅色细件。封印紫已被封印黑取代、不再用于棋盘(色史归 git 提交史);红族棋盘侧只剩危险红;主角随身背包件为朱红矩形件(人设侧见 character-design),与棋盘封印色脱钩。验收:机检〈封印宣告色 = #000000,未开格墙顶对其对比 ≥4.4:1〉 ←原一;P2 +- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,旗杆米白保可读。旗朱与封印紫已不用于棋盘封印旗,红族棋盘侧只剩危险红;人设背包旗 = 旗朱红矩形旗,与棋盘封印色脱钩(人设侧见 character-design 背包旗条)。验收:机检〈封印宣告色 = #000000,未开格墙顶对其对比 ≥4.4:1〉 ←原一;P2 - **危险红 `#A8321F`**|危险色:雷 = 被惊动的巢穴,兑现档红,更深更沉。验收:机检〈危险语义色 = #A8321F〉 ←原三;P2 - **奖励金 `#F0A828`**|已兑现的收益:GP/宝箱/蜡封,全作唯一亮金。验收:机检〈收益语义色 = #F0A828〉 ←P2;§2.3/2.4 - **小人蓝 `#2E86DE`**|小人主色:棋盘唯一"己方",与光标准线同族。验收:机检〈己方语义色 = #2E86DE〉 ←P1;§2.1 @@ -215,7 +215,7 @@ - **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#A8AEB8`)→ 并入 `FOG_MEMORY_INK #C4C4C4`,**部件取值 = `#C4C4C4`**;② 主角提灯铜色(原拟 `#C08A3A`)→ 并入 `GOLD_DEEP #B07A14`,**部件取值 = `#B07A14`**;③ 食人魔酋长暖褐皮(原拟 `#B08050` / `#A97C4E`)→ 并入 `暖石 #B8A582`,**部件取值 = `#B8A582`**;④ 石君王灰皮(原拟 `#9A968C` / `#949089`,按**次级皮**口径——板甲视同体块主色、灰皮属次级皮故不受 L 0.22–0.45 约束)→ 并入 `STONE #786E62`,**部件取值 = `#786E62`**;⑤ 轻甲护甲图标木色 `#8A5A34`(深素底下 2.72:1 不过门;原拟新增木色亮档 `#C49A5E`)→ 并入 `WARM_FAINT #CDB88C`,**部件取值 = `#CDB88C`**(8.08:1)。**一色两岗代价已随裁定接受**:① 的 `#C4C4C4` 语义岗 = 迷雾记忆墨(§6.5)、② 的 `#B07A14` 属金族(§4.2 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉 - **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①地台顶缘受光线 `#C4AC84` 已并入 `#C9AF83`(掩尘枚 `BRICK`,与 §8.2 高墙口沿高光同枚)——源常量已删除(全仓零引用,退役断言测试串为预期例外),绘制点全部改指 `#C9AF83`;②地台描边 `#5A4C3A` 已并入 `#4C4038`(代码 `PLAZA_EDGE`,ui_kit 唯一落点)——源常量已删除,绘制点全部改指;③舞台底 `#3E342A`(代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)——已随 §6.1 舞台背景插画批处置完毕(2026-11-02 迭代 48):源常量已删除(常量表零键),背景资产接管该绘制面,程序化平色缺图回退改指 `SLOT_BACKDROP`(`#3E362C`)。程序化回退绘制与已落合并值的贴图保持一致。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉 -**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),封印印记、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9 +**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9 **色板增量纪律(数值状态 = 待定现用)**:全作色板硬上限 **48 色**(防色板无限膨胀的封口条款;**额度已用尽、余量 0**,后续余量以色板检查器实报为准);新色入板须同时过两道门:①§4.2 语义检查(不占语义色段/禁入带);②**与既有任一色的 RGB 三通道差禁止全部 <8/255**——即三通道里至少一通道 ≥8/255 即过门,「三通道差全部 <8」才算不过门(条文原写的「三通道差 ≥8/255」有「逐通道全过」与「禁止全灭」两种互斥读法,前者下现行色板 247 对不达标、与本节自认的「现行即执法」合规状态不能同真,故取后者;用户裁)。**额度已用尽:后续任何新增须先压缩或合并既有色**——按本节「额度不足时呈报作者」口径呈报,**压缩或合并哪几枚由作者裁,本节不代裁、不预指对象**。反例实证:#3A2E22 ↔ #362B20 三通道差均 <5,肉眼不可辨,同板共存只增噪。不过门即触发「换色合并」(把意图色并进最近既有色),**禁加色绕行**;加色义务同时登记于本表修订。**「待定现用」态释义**:数值**现行即执法**(色板检查器按此拦,§8.3)——非候选(不存在"等冻结"空窗)、非死规(后续需求引入时可变);调整走本节修订并登记,改值 = 修订不是违约。验收:机检〈色板总数 ≤48;新色入板过两道门〉 @@ -420,7 +420,7 @@ - **菱形灯光区(遮罩几何)**:灯光区 = 以角色所在格为中心、**曼哈顿距离 ≤ LIGHT_RADIUS** 的格集,区内照常渲染。LIGHT_RADIUS 走表值/常量参数化,**基准 8**——现行事实源「灯光区起步半径=微光环 8 格」的落码取值(半径数值沿微光「周围 8 格」的 8 起步,实测后可调)。**两圈分立注**:类型微光作用圈 = 英雄贴身 8 邻格(与可挖域同圈,现行事实源《HUD框架》「未开格类型微光」节),**不随灯光区扩**(权威文档配套裁决)——灯光区只裁可见性,微光锚仍是可挖域,两圈几何分立、半径参数互不读写。提灯插值位:FOG_LANTERN_BONUS = **+2**(flat 整数档,权威文档配套裁决;落码常量名 `board_view.gd FOG_LANTERN_BONUS`),**已接效果入库**(载体=秘宝·同调位 fog_lantern「魔法提灯」行,组合根每战装配链显式重算接线;呈现画法零改动——本节只管"怎么画",机制现行事实源仍归《HUD框架》)。边界随角色格位逐格重算,无半径插值动画。←现行事实源;§2 光照氛围允许域 - **区外未开格:全黑不渲染(不透明档,照抄现行事实源)**:现行事实源原文「区外**未开格=全黑不渲染**」——不透明纯黑(L=0),**非高遮蔽半透档**。理由注记(为何不用高遮蔽档):①三档信息阶梯干净——未开格不可见=零信息、记忆格=弱信息、活格=全信息,叠读不打架;②高遮蔽档下砖面纹理隐约残留,与「类型微光=未开格唯一信息通道」(§4.2/§4.6.3)冲突——纹理残影泄格面观感;③现行事实源验收线「区外未开格不可见」须二值态才能逐格断言。←现行事实源;§4.2 -- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地板纹理原样 + 灰罩压顶**——纹理直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。双峰板色牌格在记忆态**退牌归板**——牌面并入记忆态、数字以记忆墨直画。**封印印记不在记忆态面**:印记只落在未开格,区外未开格全黑——印记随格隐没、灯光区移回复显(纯呈现,封印数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2 +- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地板纹理原样 + 灰罩压顶**——纹理直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。双峰板色牌格在记忆态**退牌归板**——牌面并入记忆态、数字以记忆墨直画。**旗不在记忆态面**:旗只插在未开格,区外未开格全黑——旗印记随格隐没、灯光区移回复显(纯呈现,旗数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2 - **记忆态可读性判据**:数字对合成底**归一后(真实渲染面)三关 ≥4.5:1**(WCAG 硬指标;合成底 L1 #4C4038/L2 #3F4147/L3 #3E4048;行值与素材随动,测点=ui_contrast_probe 迷雾段)。**归一注**:装载亮度归一是现行生产路径(`board_view._normalize_floor_luma`),原图均值≠渲染面,复算须落归一后底。可辨性自查(三通道,随样张/实机自检):①对全黑未开格——合成底 #3E3731(L≈0.036)对 L0,实机目检常光档可分(暗房档一并抽检);②对灯光区内活态已开格——主判据=数字墨灰度差(记忆墨 L0.55 对亮墨三档 L≥0.75 / 色牌 #F5EAD3 L≈0.83,灰度 ΔL≥0.20)+ 色相通道(记忆态近中性灰,对活态暖底暖墨);③格态三形在记忆态保持灰度可辨——形通道经纹理+灰罩形态不变,沿 §1.4 验收 2 同源判据。验收:机检〈ui_contrast_probe 迷雾段记忆墨对比 ≥4.5:1〉←§1.4 - **迷雾过渡边:硬边(逐格二值)**:灯光区边界 = **整格二值切换**(区内全渲染 / 区外全黑或记忆态),**不做渐变过渡带、不做半透明晕边**。理由:①边界是信息边界不是氛围晕——渐变中间态 =「看得见但看不清」,破数字可读硬指标与记忆态明度档;②现行事实源验收断言(边界格呈态、两态逐位一致)须格级二值可判;③与砖面整格表达(§3.2 高墙瓦片体系)、ITB 格内信息分层气质同轴。无动画,减动效无涉。←现行事实源;§1.2 原则一;§3.2 - **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉 @@ -513,7 +513,7 @@ ### 8.2 像素基准与画布 - **缩放纪律(硬规则,按资产两类分级;不做个案豁免)**: - - **甲类·文本与语义件**(数字、图标、封印印记、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 + - **甲类·文本与语义件**(数字、图标、旗、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 - **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉 - **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×、**护甲类型图标三枚 24px=1×**——源 24×24,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉 - **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉 @@ -522,7 +522,6 @@ - **画布基准(暂定值)**: - **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致); - **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销; - - **封印印记源档 = 32×32、显示 1×**(甲类整数倍纪律):形 = 外环+十字(形与画法见 §3.2)。定值——**外环外径 23px(半径 11.6px)、环壁厚 2.6px;十字两臂各宽 3.2px、四端止于环内沿半径 9px(与环相接);圆心=格心;最外沿距格边 ≥4px**(该 4px 空带归变异纹带,§3.2 变异格形状通道)。纯黑实心剪影,单图像素域色数 = 1(底色透明)。改形须本节与 §3.2 同批修订。验收:目检〈件为 32×32 单色;外沿距格边 ≥4px;十字四端与环相接〉 - **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉 - **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉 - **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉 @@ -537,7 +536,7 @@ ### 8.4 帧数规格(样张冻结) - **帧数规格(样张冻结)**:待机循环 4 帧;攻击/技能准备 6 帧单次;受击 2 帧;死亡 = 灰化+碎裂退场(§4.6.4 语义硬规则),序列帧归 ✦ 缺料清单;帧时长与结算节拍对齐(预告时序见 §4.6.4)。验收:机检〈序列帧数与档位一致〉 -- **方向集**:棋盘小人=**一份画稿+左右镜像**(现行资产=朝左帧两态 idle/walk,右向 = `flip_h`;下/上向无独立帧);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟口径已追认(棋盘小人朝左时旗面朝左,与立绘侧「旗面恒画右」并存;画稿维持一份 + 镜像,零新增资产)。验收:目检〈侧向镜像无破绽〉 +- **方向集**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟口径已追认(棋盘小人朝左时旗面朝左,与立绘侧「旗面恒画右」并存;画稿维持一份 + 镜像,零新增资产)。验收:目检〈四向帧齐、侧向镜像无破绽〉 ### 8.5 结构件完整性与参考核验 diff --git a/design/art/art-status-checklist.md b/design/art/art-status-checklist.md index 3f8ed430..ac1138eb 100644 --- a/design/art/art-status-checklist.md +++ b/design/art/art-status-checklist.md @@ -27,7 +27,7 @@ |---|---|---| | 格面资源徽章 19 枚 | ◐ | [art-bible](art-bible.md) §4.6 | | 关卡节点图图标组 | ◐ | [art-assets-checklist](art-assets-checklist.md) §3.2(正式 UI 设计未成文) | -| 雷/封印印记/数字砖面全套 | ◐ 封印印记已换代(环+十字法阵);雷与数字砖面仍待格子样张前置 | [art-bible](art-bible.md) §1.4/§3.2 | +| 雷/封印印记/数字砖面全套 | ◐ | [art-bible](art-bible.md) §1.4/§3.2(前置:格子样张) | | 增益状态效果图标 9 枚 | ✅ 已接入(2026-09-24) | [art-bible](art-bible.md) §4.6.1(候选原稿:状态图标批 2026-09-23 远程出图,归 candidates 档——路径描述性引用,归档纪律见 art-bible §8.3) | | 营地场景图 | ◐ | [art-bible](art-bible.md) §6.2 | | 字体(全局 UI 像素字,Fusion Pixel 单套单字重) | ✅ 已定稿在役(2026-09-27 接入;2026-10-24 用户裁现状即终稿——不另接拉丁专件、不上第二字重) | [art-bible](art-bible.md) §7.4 | @@ -38,7 +38,7 @@ |---|---|---| | 受击反馈 / 死亡表现 / 顺位标记 | ◐ 程序化演出在役(受击闪白+打击火花、灰化+碎裂退场〔禁瞬消〕、顺位徽标 ①②③ 程序绘制;帧级素材未产,§8.4 帧数规格候选) | [战斗表现](../gdd/systems/战斗循环/战斗表现.md) | | 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 | -| 封印特效(动作=封印,两件:施印特效+印记常显) | ◐ 印记常显已落位(`seal_mark.png` 环+十字);施印瞬间特效缺 | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | +| 封印特效(动作=封印,两件:施印特效+印记常显) | ⬜ | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | | 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) | | B 区舞台背景三关变体+共用连续地面层(场景件) | ✅ 已交付(2026-10-31 入包 `game/assets/art/scene/`:三张背景 1280×252、共用地面 1280×100;地面按作者裁保留候选 v12b 原亮度 L≈0.132) | [art-bible](art-bible.md) §6.1 · [battle-stage-prompts](battle-stage-prompts.md) §2 | diff --git a/design/gdd/02-系统清单.md b/design/gdd/02-系统清单.md index d6dc14b5..9b2fb5d0 100644 --- a/design/gdd/02-系统清单.md +++ b/design/gdd/02-系统清单.md @@ -15,7 +15,7 @@ | `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) | | `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 | | `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 | -| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.27**,2026-10-13) | +| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.26**,2026-10-13) | ## systems/ 五个循环 diff --git a/design/gdd/08-Alpha版本标准.md b/design/gdd/08-Alpha版本标准.md index 2d60b9f4..606f7bf1 100644 --- a/design/gdd/08-Alpha版本标准.md +++ b/design/gdd/08-Alpha版本标准.md @@ -2,7 +2,7 @@ > **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。 > **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。 -> **状态**:**已定稿(v1.27)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。 +> **状态**:**已定稿(v1.26)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。 > **成文**:2026-10-13。 ## 本文怎么用 @@ -145,7 +145,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战 |---|---|---| | 像素美术风格与色板 | [美术圣经](../art/art-bible.md) | 在役 | | 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 | -| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史;观感复核随战斗一屏样张过闸完成,凭据=体验验证清单 ㊶) | +| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史,复核转正归战斗一屏样张) | | 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.md) | 待做(开工前置=先做素材轴/UI 轴对照截图定性,再按定轴修复;旧卡 v1.4 存 reports/ 作素材、非已写卡状态,重启须重新立卡;出口=用户实机目检第二、三关不再判「看着费劲」) | | 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 在役(九件已按档落位+机检全绿——原稿分辨率入包禁降采样、显示按三档绝对高 96/144/188 缩放、现有形象即终稿;定值单点=art-bible §8.2;实机目检清晰度与同屏体量顺序登记体验验证清单 ㊻) | | 敌人立绘(全敌种) | [角色设计](../art/character-design.md) | 在役 | @@ -156,7 +156,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战 | 减动效开关 | [设置与本地化](systems/表现与支撑/设置与本地化.md) | 在役 | | 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) | | UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(2026-10-24 用户裁现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) | -| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 在役(规格半=目标规格清单已落 art-bible 五张规格表〔2026-10-30 合回〕,五项实装定值与样张机检随迭代 43 入库;出口半=方向稿用户目检已通过,样张整体过闸,凭据=体验验证清单 ㊶。主角+三 Boss 重产依赖照旧;站桩帧动画已改排 Beta,见「六 · Alpha 不含」) | +| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 部分在役(规格半已闭合=目标规格清单已落 art-bible 五张规格表〔2026-10-30 合回〕,五项实装定值与样张机检随迭代 43 入库;未结半=出口未达——方向稿用户目检(体验验证清单㊶)先行,样张整体过闸随其后。主角+三 Boss 重产依赖照旧;站桩帧动画已改排 Beta,见「六 · Alpha 不含」) | | UI 氛围层与九宫格切片观感修 | [待办清单](../../待办清单.md) | 部分在役(光柱硬边已修=连续 alpha 渐变;未结半=九宫格切片错位——Alpha 内做最小修复:核对素材尺寸,必要时重出九宫格素材,全套素材重出排 Beta;出口=结算屏与图例屏错位消失,渲染闸+用户目检通过) | | UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:①滑轨填充随迭代 25 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) | diff --git a/design/gdd/systems/战斗循环/战斗表现.md b/design/gdd/systems/战斗循环/战斗表现.md index 6165b5bd..8500de82 100644 --- a/design/gdd/systems/战斗循环/战斗表现.md +++ b/design/gdd/systems/战斗循环/战斗表现.md @@ -1,7 +1,7 @@ # 战斗表现 > 一句话:战斗动画与演出(旧体系未单独成文) -> 状态:范围草案(待设计);现行为 v1.3 +> 状态:范围草案(待设计);现行为 v1.2 ## 设计目的 @@ -70,7 +70,7 @@ ### 前置美术测试是硬前置 -正式设计本文之前,必须先做一次"战斗表现美术测试":用一屏样张(一张按候选方案画成的试验图)锁定角色剪影、动画方案、战斗空间三件事。这是旧体系评审登记的排期硬前置,不是建议;battle.md 也登记了该测试须按双舞台布局出样。样张未过,不启动任何终稿资产与动画设计。**该硬前置已解除**:一屏样张整体过闸(凭据=体验验证清单 ㊶)。 +正式设计本文之前,必须先做一次"战斗表现美术测试":用一屏样张(一张按候选方案画成的试验图)锁定角色剪影、动画方案、战斗空间三件事。这是旧体系评审登记的排期硬前置,不是建议;battle.md 也登记了该测试须按双舞台布局出样。样张未过,不启动任何终稿资产与动画设计。 附着在同一次样张上的还有一项已登记的裁决(登记出处 = 敌人系统的呈现契约):敌人层绘制优先级的插入位置——白话就是敌人的姿态精灵画在绘制顺序的哪一层,会不会、以及如何与棋盘上的数字、封印印记、小人发生遮挡。它随美术测试一并裁决,不在本文提前定。 diff --git a/design/gdd/ux/battle.md b/design/gdd/ux/battle.md index 2c38522d..ca1b89a2 100644 --- a/design/gdd/ux/battle.md +++ b/design/gdd/ux/battle.md @@ -125,7 +125,7 @@ > 布局为 B/C/D 三区——关卡进度条不在战斗屏(节点链进度常驻关卡画面,见 Navigation Position;字母不重编沿用 B/C/D)。 -> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;印记贴图已落位(`seal_mark.png`,环+十字法阵,形与定值见 art-bible §3.2/§8.2),缺图时回落封印黑「封」字形(棋盘视图程序化回退);封印特效的施印瞬间一件未实装。封印色史=朱红 → 封印紫 → 封印黑(现行)。 +> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;封印特效未实装,占位 = 封印黑「封」字形(棋盘视图程序化占位,现行代码仍为旧「旗」字形、随代码术语批换代,贴图在位=字形退役;旗朱→封印紫 2026-09-26→封印黑 2026-09-28 用户拍板)。 > 可挖域微光画法定稿(2026-09-25 用户拍板):**接触带·空间侧 + 蓝族**——光画英雄格周界接触面(正交邻墙=整边,斜向邻墙=角点+两半边),详见 art-bible §3.2;现行墙格整框绿相呼吸为待退役占位,实现切换归表现批(探针断言顺带)。 diff --git a/game/assets/art/board/seal_mark.png b/game/assets/art/board/seal_mark.png index 225c28a09cec41d8a35b2962b3ae5654d3dff65b..ddf875e15783f8f27cb6e9e0fab2664f9ec9280c 100644 GIT binary patch delta 93 zcmcc5SUo`{+{x3$F{EP7+be-w42nF=7Y!@^Ywu2v5Ky*wzx%=k2S+Vw`{&o+#V*ug wJHT}+iQ&|A3Db2@1T$? z9a<dbItu9LRJl-U727JS>RYQJE0R6rVyPw)CuK w4=5cW&4gt{i!Zn0tzR;H bool: # 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。 -# 转蓄力层〔上限 3〕替代 RAP 转存——「不动也是行动」;cleared 分支 AP 恒清零维持作废语义。 +# cleared 分支 AP 恒清零维持作废语义。 # 本方法只记账——剩余 AP 存 _last_stop_ap 后清零,转存决策在 round_end 发射点 -# finish_round_stop(grant_ap(mode:transfer) 订阅定夺)。 +# finish_round_stop(转存率定夺)。 func request_stop() -> bool: if _phase != Phase.ACTIVE: _reject("phase") @@ -193,18 +187,11 @@ func grant_reserve_ap(n: int) -> void: _rap += maxi(0, n) -# 轮末停手转存决策(round_end 发射点调用——编排器授予段尾):transfer=true=剩余 AP 转蓄力层 -# (上限 CHARGE_LAYER_CAP,整备军备面);false=按转存率转存 RAP(transfer_pct 百分比口径缺省 100=1:1; -# 储备罐 rap_transfer_mult magnitude 125=1.25:1,整数除法向下取整=每 4 AP 多转 1)。 -# 非停手轮(cleared/耗尽)_last_stop_ap=0 两分支均零操作。转蓄后达上限的沿发 charge_full(R6 框架先行——现无订阅者)。 -func finish_round_stop(transfer: bool, transfer_pct: int = 100) -> void: - if transfer: - var prev_layers := _charge_layers - _charge_layers = mini(CHARGE_LAYER_CAP, _charge_layers + _last_stop_ap) - if _port != null and _charge_layers >= CHARGE_LAYER_CAP and prev_layers < CHARGE_LAYER_CAP: - _port.emit("charge_full", {"layers": _charge_layers}) - else: - _rap += _last_stop_ap * maxi(1, transfer_pct) / 100 +# 轮末停手 RAP 转存(round_end 发射点调用——编排器授予段尾):按转存率转存 RAP +# (transfer_pct 百分比口径缺省 100=1:1;储备罐 rap_transfer_mult magnitude 125=1.25:1, +# 整数除法向下取整=每 4 AP 多转 1)。非停手轮(cleared/耗尽)_last_stop_ap=0 零操作。 +func finish_round_stop(transfer_pct: int = 100) -> void: + _rap += _last_stop_ap * maxi(1, transfer_pct) / 100 _last_stop_ap = 0 diff --git a/game/core/battle_fx_host.gd b/game/core/battle_fx_host.gd index 67478a1d..d789cd2c 100644 --- a/game/core/battle_fx_host.gd +++ b/game/core/battle_fx_host.gd @@ -1,5 +1,5 @@ ## 效果引擎宿主件(自 battle_orchestrator 收编 fx 族):事件发射面(fx_emit——18 发射点统一入口)、 -## 补充事实判定(charge_layers 自答,余五事实转宿主)、主角 AC 三值累积与同步(本轮/本战累积器+ +## 补充事实判定(biz_fact 闭面四键)、主角 AC 三值累积与同步(本轮/本战累积器+ ## 基线快照)、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含再入闸—— ## 同步信号回环防二次缓冲二次兑付)、round/battle 桶计数器持有(桶随编排器生灭语义不变)、 ## round_start 发射与施加、订阅重建、桶挂引用。生命周期=随编排器(编排器 _init 同建同灭,每战新建)。 @@ -34,7 +34,7 @@ var ac := {"base": -1, "round": 0, "battle": 0} # ac 束(base=主角 AC 基 var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末宿主 bonus_delta_drain 读后清零) var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域) var reveal := {"queue": [], "busy": false} # reveal 束(queue=揭示效果待兑队列〔见血开窍/斩首学者/地质学家/跳格采掘共用——授予段末统一兑付,防轮 facts 迭代中追加重入〕;busy=兑付中标记——宿主 _on_cell_revealed 缓冲旁路判定,防二次缓冲二次兑付) -var facts_inject := {"dig_streak": 0, "rider_tier_bonus": 0, "rider_tier_crit": false} # 业务发射事实束(键集封闭——原 damage.inject 运行态束迁宿主:dig_streak=combat_segment 每轮写;rider 两键=begin_battle 每战复位;charge_layers 不在束内=活读 action_economy) +var facts_inject := {"dig_streak": 0, "rider_tier_bonus": 0, "rider_tier_crit": false} # 业务发射事实束(键集封闭——原 damage.inject 运行态束迁宿主:dig_streak=combat_segment 每轮写;rider 两键=begin_battle 每战复位) func _init(bundle: Dictionary) -> void: @@ -89,13 +89,11 @@ func counter_add(counter_id: String, value: int, set_form: bool) -> void: EffectEngine.counter_add(counters_ctx, counter_id, value, set_form) -# 业务发射事实面(闭面五键——三 when 事实 {charge_layers 活读/dig_streak 轮内槽/hp_ratio 主角 +# 业务发射事实面(闭面四键——三 when 事实 {dig_streak 轮内槽/hp_ratio 主角 # 血量比例算式逐字承 damage_controller 原式}∪ 两 rider 运行态读数 {facts_inject 现值}; # 其余事实一律 null——battle_index/round_index/seals_count/nest_count 禁进,扩面=行为变化) func biz_fact(name: String) -> Variant: match name: - "charge_layers": - return action_economy.charge_layers "dig_streak": return int(facts_inject["dig_streak"]) "hp_ratio": @@ -216,11 +214,9 @@ func reveal_busy() -> bool: return bool(reveal.busy) -# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数;charge_layers 本件自答 -# 〔action_economy 直读〕,余五事实转宿主 facts_cb——宿主侧单点,未知事实回落 null) +# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数; +# 全部转宿主 facts_cb——宿主侧单点,未知事实回落 null) func _fx_fact(fact: String) -> Variant: - if fact == "charge_layers": - return action_economy.charge_layers return facts_cb.call(fact) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 56e121e0..9f772995 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -110,7 +110,7 @@ func _book_negative_blast() -> void: _rounds.mines_hits["total"] += 1 -# 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实;charge_layers 归 fx_host 自答;未知事实回落 null) +# 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实;未知事实回落 null) func _fx_host_fact(fact: String) -> Variant: match fact: "nest_count": diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 09f9344c..e095f8f4 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -268,8 +268,7 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary: # 按序改写族四拍+掷点快照(armor_roll 发射点在此):第一拍矿工重锤命中交换(正负双修原生形态: # 命中判定减成 trade_hit 点〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望; # 改写位先于掷点折扣读数)→ 第二拍专注免掷骰恒定折扣(方差买断成期望——稳定器人格;roll 记 0 归因) -# → 第三拍厚积薄发满层必中(spike ready 时 discount 恒全额、暴击面走倍乘不加掷;when charge_layers≥3 -# 在 dispatch 判定——满层持续状态口径)→ 第四拍猎弱本能对轻甲敌折扣档改善 value 档 +# → 第三拍满层必中(spike ready 时 discount 恒全额、暴击面走倍乘不加掷)→ 第四拍猎弱本能对轻甲敌折扣档改善 value 档 # (对冲 pierce 列 L1 −1 开局罚;改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复)。 # clamp_check 同在此发射(唯一发射点——命中随返回值穿给 _ctx_clamp_delta)。 func _ctx_rewrites(t: Dictionary, target: Dictionary, armor: String, matchup: String) -> Dictionary: @@ -335,7 +334,7 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float: 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。 + # 加法族:attack_bonus per 计数器层值(层数×value 加算)。 # value_counter 扩展〔疼痛记忆/弃图兑换/陷阱本能——rider 值=计数器账面, # 值读于施加时点=命中序先行 rider 后清零行〕 for h in ac_hits: @@ -349,7 +348,7 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float: if String(h.get("kind", "")) == "counter_add": var set_v = h.get("set", null) _port.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) - # 任意真族:厚积薄发满层 spike 倍乘(≥3 层——强制必中已在折扣拍承载) + # 任意真族:modify_ticket mode=spike 满层倍乘(强制必中已在折扣拍承载) if ctx["spike_ready"]: for h in ac_hits: if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "spike": @@ -430,7 +429,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void: var hero: Dictionary[Variant, Variant] = units.hero_unit() var roll := _enemy_roll(t) var total: int = int(roll["total"]) - # damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers), + # damage_taken 发射(kind=enemy_skill;shield 减伤订阅面:加法族合计 value×per 计数器层值, # 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/ # counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 damage_taken 载荷携带) var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index)) diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd index 5b66a023..cc0238bb 100644 --- a/game/core/effect_engine.gd +++ b/game/core/effect_engine.gd @@ -11,7 +11,7 @@ extends RefCounted ## 装板完成〔巢穴预告——每板恰一次,战斗板生成/重建发射点〕) const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commit", "clamp_check", "armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle", - "round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed", + "round_end", "reward_roll", "treasure_used", "dig_stop", "battle_won", "seal_missed", "board_installed", # 换图系双事件(refresh_request=玩家请求刷新〔省力周转 apply_discount 订阅面——折扣只改门槛 # 与扣费不改拒绝判据〕;board_refreshed=成功刷新事实〔换图系七件订阅主挂点——装新板后才发射〕) @@ -56,7 +56,7 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open ## 当时有封印〔破封一击〕;ap_exhausted_last=上轮 AP 耗尽〔一滴不剩〕;hp_ratio=生命比例〔背水 ## 06 前置条兑付〕;amount=单次伤害量〔保险丝〕;ap_left=停手剩余 AP〔弃图兑换〕) const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_armor_class", - "seals_count", "charge_layers", "nest_count", "round_index", "counter", + "seals_count", "nest_count", "round_index", "counter", "new_resource_type", "is_edge_cell", "is_blank_zero", "dug_under_seal", "ap_exhausted_last", "hp_ratio", "amount", "ap_left", "pct", "battle_index"] # 关卡域战斗序(1 基,enc 携带;开局姿态组「每关首轮窗」条件面) @@ -200,7 +200,7 @@ func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary) _triggers.append(t) -## 订阅查询面(发射点缺省行为仲裁用——如 round_end 无 transfer 订阅时走 RAP 转存) +## 订阅查询面(发射点缺省行为仲裁用——round_end 走 RAP 转存) func has_effect_on(event: String, kind: String, mode: String = "") -> bool: for tr in _triggers: if String(tr.on) != event: @@ -458,11 +458,11 @@ static func _vtr_from_refs(rid: String, tr: Dictionary, row: Dictionary, errs: A ## 组合规则(需求卡片 §3.4 六族——效果类型的必填属性,发射点施加面引用) -## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap(非 transfer)、 +## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap、 ## counter_add、shield——发射点逐条累加收集器。 ## 乘法族(收集后一次施加,正负分桶):yield_mult、damage_mult。 ## 任意真族(任一命中即生效):spawn_spell、spike。 ## 按序改写族(收集→单点按声明序施加):modify_ticket 及一切改写窗口效果。 ## 按序消耗族(按订阅序消耗共享资源,耗尽即止;可选 fallback):counter_spend。 -## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、grant_ap(mode:transfer)、 +## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、 ## rewrite_board、apply_status——发射点逐条执行。 \ No newline at end of file diff --git a/game/core/effect_port.gd b/game/core/effect_port.gd index c3b3c2e0..e562821e 100644 --- a/game/core/effect_port.gd +++ b/game/core/effect_port.gd @@ -1,7 +1,7 @@ ## 效果端口件:战斗业务类(DamageController/ActionEconomy)唯一的效果发射出口——收事件与载荷、 ## 交效果引擎结算、取回命中批。六 Callable 路由宿主(BattleFxHost.fx_emit_biz/counter_read/ ## counter_add/biz_fact/enqueue_reveal/apply_ac_bonus),经组装期 bind_port 单点注入。 -## 闭面红线:事实回调=biz_fact 闭面五键(charge_layers/dig_streak/hp_ratio/rider_tier_bonus/ +## 闭面红线:事实回调=biz_fact 闭面四键(dig_streak/hp_ratio/rider_tier_bonus/ ## rider_tier_crit),battle_index/round_index/seals_count/nest_count 禁进业务发射事实面。 ## 单向接线:本件只持宿主注入的 Callable,禁持 BattleFxHost 对象引用(宿主 bundle 已持两业务类 ## ——对象引用即成 host→damage→port→host 强引用环,两件均随每战新建=泄漏)。 @@ -50,7 +50,7 @@ func counter_add(counter_id: String, value: int, set_form: bool) -> void: counter_add_cb.call(counter_id, value, set_form) -# 事实取值(闭面五键宿主侧单点——未绑→null) +# 事实取值(闭面四键宿主侧单点——未绑→null) func fact(name: String) -> Variant: if not fact_cb.is_valid(): return null diff --git a/game/core/settlement/combat_segment.gd b/game/core/settlement/combat_segment.gd index 0c047327..baa2d976 100644 --- a/game/core/settlement/combat_segment.gd +++ b/game/core/settlement/combat_segment.gd @@ -1,7 +1,7 @@ class_name CombatSegment extends RefCounted # ③ 战斗段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 -# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 蓄力层注入 + 主角先手 +# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 主角先手 # + 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。 # 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。 # 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)—— @@ -32,8 +32,6 @@ func run(ri: int) -> void: elif evt.kind == "mine": round_mine = true oc.fx_host().facts_inject["dig_streak"] = 0 if round_mine else round_safe - # 蓄力层不再逐轮注入(效果端口件闭面——fact charge_layers 由宿主 biz_fact 活读 action_economy - # 现值:唯一写点=finish_round_stop 于上轮授予段尾,本轮授予段发射前无第二写点,活读等价) _hero_strike(ri) _enemy_actions(ri) _board_deferred_settle(ri) diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index 6cdec2ab..e66515a2 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -259,8 +259,7 @@ func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionar # round_end 发射+施加(每轮结算收口:熔火之躯反打单开立〔open_ticket 即时执行〕+积层清零〔counter_add -# set〕;整备转蓄力由 finish_round_stop 承载——停手侧 AP 已在 request_stop 记账,转存决策=grant_ap -# (mode:transfer) 订阅定夺,无订阅走 RAP 转存缺省)+施加面:grant_gp〔爆破会计/收盘——escrow 战斗域 +# set〕;停手侧 AP 已在 request_stop 记账,finish_round_stop 按转存率转存 RAP)+施加面:grant_gp〔爆破会计/收盘——escrow 战斗域 # 缓冲,随 gp_dropped 上抛〕/grant_ap mode:queue〔封印回响——返 1 AP 下轮,queue_ap_delta 通道与轮始 # 加成同面〕+段界收口(转存率入参、清图奖励、揭示兑付、段界结账) func _round_end_apply(ri: int) -> void: @@ -278,8 +277,7 @@ func _round_end_apply(ri: int) -> void: oc.action_economy.queue_ap_delta(int(h.get("value", 0))) # 转存率入参(储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;未装配=100 与 1:1 逐位一致) var rap_pct: int = oc.attune_param_sum("rap_transfer_mult") - oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"), - rap_pct if rap_pct > 0 else 100) + oc.action_economy.finish_round_stop(rap_pct if rap_pct > 0 else 100) if oc._facts.path == "cleared": reward_grant.grant_clear_reward(ri, oc._facts.board_from_midround, oc.perfect_sweep_earned()) fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 4e803b0a..2e1e9eb6 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -210,7 +210,7 @@ func _suites() -> Array: {"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入) - {"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力层/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走 02 关联档选跑,卡片退役后补登记由 02 全套件承接) + {"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走 02 关联档选跑,卡片退役后补登记由 02 全套件承接) {"path": "res://tests/unit/battle_settlement/variant_placement_test.gd", "expect": 0}, # 变异布置宿主(战内布置段门控与数量公式/全局变异兜底回落与资源面偏斜——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) {"path": "res://tests/unit/battle_settlement/extended_row_violation_test.gd", "expect": 1, "negative": true}, # 卡④ wr2-np 扩展行必载 effect_id 负路径(行协议运行期违约面首个负面套件——独立进程豁免错误扫描) diff --git a/game/tests/unit/battle_settlement/effect_port_test.gd b/game/tests/unit/battle_settlement/effect_port_test.gd index acb419c8..4790e064 100644 --- a/game/tests/unit/battle_settlement/effect_port_test.gd +++ b/game/tests/unit/battle_settlement/effect_port_test.gd @@ -13,7 +13,7 @@ const ConfigDBScript := preload("res://core/config_db.gd") const EffectEngineScript := preload("res://core/effect_engine.gd") const TEST_SEED := 42 # 夹具种子(基建值非玩法数值——同种子两跑对比的隔离前提) -const LAYER_COUNTER := "swap_charge_layers" # 整备军备蓄力层计数器(feats.tres charge_rotation 行) +const LAYER_COUNTER := "swap_charge_layers" # 蓄力周转计数器(feats.tres swap_charge_layers 行) var _db @@ -54,7 +54,7 @@ func _hero_attack_value(rows: Array) -> int: func _smoke_effect_port_wiring() -> void: print("--- 效果端口:装配链单实例+真实发射路由 ---") - # 充能跑(整备军备在册——attack_commit 订阅行 attack_bonus per 蓄力层计数器) + # 充能跑(蓄力周转 charge_rotation 在册——attack_commit 订阅行 attack_bonus per 蓄力周转计数器) var orch_c = _orch(TEST_SEED) var rows_c: Array = [] orch_c.step_row.connect(func(row): rows_c.append(row)) @@ -64,7 +64,7 @@ func _smoke_effect_port_wiring() -> void: var port_a = orch_c.action_economy._port Kit.check("V1 端口在座(damage/action_economy 双绑)", port_d != null and port_a != null) Kit.check("V1 端口单实例(两业务类持同一 EffectPort)", port_d == port_a) - # 蓄力层预置 3(调试注入两层·数据面粒度——计数器账面直写,与 refresh_feats_test V8 同法) + # 蓄力周转预置 3(调试注入两层·数据面粒度——计数器账面直写,与 refresh_feats_test V8 同法) EffectEngineScript.counter_add(orch_c.fx_host().counters_ctx, LAYER_COUNTER, 3) # 最小回合:停手即轮末结算(主角先手攻击在战斗段同步兑付——零 await 同步链) orch_c.minesweeper.settle_batch([{"action": "stop"}]) @@ -80,7 +80,7 @@ func _smoke_effect_port_wiring() -> void: orch_0.minesweeper.settle_batch([{"action": "stop"}]) var v_0 := _hero_attack_value(rows_0) Kit.check("V1 对照跑攻击行在流", v_0 >= 0) - Kit.check("V1 delta 读数非零(蓄力层加算进入主角攻击基值)", v_c - v_0 >= 1) + Kit.check("V1 delta 读数非零(蓄力周转加算进入主角攻击基值)", v_c - v_0 >= 1) # ---- V2 业务类零引擎直连(源码契约锚——theme_projection_test 源码锚惯例)---- @@ -100,7 +100,7 @@ func _test_biz_fact_face_closed() -> void: print("--- 效果端口:业务发射事实面闭集 ---") var orch = _orch(TEST_SEED) Kit.check("V3 begin_battle 受理", orch.begin_battle(_enc([]))) - # 合成行直改数据面(调试注入两层·数据面粒度):effect 段逐字抄现役铁壁 shield 表行(kind/value 零推表); + # 合成行直改数据面(调试注入两层·数据面粒度):effect 段逐字抄现役 shield 表行(kind/value 零推表); # rebuild 走持有 id 过滤真实表行——合成行经直改表行进入装载域(订阅面换代为合成行独占,确定性) var pos_id := "probe_shield_pos" var neg_id := "probe_shield_neg" @@ -127,3 +127,5 @@ func _test_biz_fact_face_closed() -> void: ctrl.flush_submit(t_b) var hp2 := int(ctrl.units.hero_unit().get("hp", 0)) Kit.check("V3 阴性对照:battle_index 不答——零减量(10→10,事实面未扩)", hp1 - hp2 == 10) + # 闭面收窄 V4(键退役验收——经 begin_battle 真实装配链取宿主应答;键在→返 0,键退役→null) + Kit.check("V4 闭面收窄:charge_layers 键退役——宿主事实应答 null", orch.fx_host().biz_fact("charge_layers") == null) diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 6a6c34cf..75d66779 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -129,19 +129,19 @@ func _init() -> void: ae6.queue_ap_delta(4 - int(db.run.action_economy.ap_per_round)) # 排队修正到测试口径 4 AP(notify_round_start 兑现) ae6.notify_round_start() ae6.request_stop() - ae6.finish_round_stop(false, 100) + ae6.finish_round_stop(100) Kit.check_quiet(ae6.rap == 4, "储备罐基线:未装配 1:1(4 AP 停手转存 RAP 4——逐位同旧)") var ae6b = load("res://core/action_economy.gd").new(db) ae6b.queue_ap_delta(4 - int(db.run.action_economy.ap_per_round)) ae6b.notify_round_start() ae6b.request_stop() - ae6b.finish_round_stop(false, 125) + ae6b.finish_round_stop(125) Kit.check_quiet(ae6b.rap == 5, "储备罐正常:125% 转存(4 AP→RAP 5=1.25:1 整除口径;不碰 AP 上限)") var ae6c = load("res://core/action_economy.gd").new(db) ae6c.queue_ap_delta(3 - int(db.run.action_economy.ap_per_round)) ae6c.notify_round_start() ae6c.request_stop() - ae6c.finish_round_stop(false, 125) + ae6c.finish_round_stop(125) Kit.check_quiet(ae6c.rap == 3, "储备罐边界:3 AP→3(floor 3.75——不越参数面直改停手结算)") # ⑦ 巢穴预告(装板后一次报数量——明细行经 step_row 信号收集) diff --git a/体验验证清单.md b/体验验证清单.md index 782a0223..469421cb 100644 --- a/体验验证清单.md +++ b/体验验证清单.md @@ -482,8 +482,6 @@ ## ㊶ 迭代 43——战斗一屏样张批方向稿目检(2026-10-30 随批后收尾登记) -> **✅ 已核销(2026-11-04)**:用户会话裁定三项按通过处理(未逐项呈报读数)——样张整体过闸成立,本批无缺陷呈报;连带 ㉘ 第 5 项群伤暗档 provisional 观感复核一并了结。回流落点见 art-bible §1.4 样张登记句与 08-Alpha版本标准「五 · 表现」两行。 - > 单卡合回(2026-10-30,git 提交 3b66d1a6):战斗一屏样张批——五项实装定值(血条危险红/条底收常量/顺位徽标提档/护甲三枚 24×24 PNG/名字行素底牌不压地台+地台件九宫格接装)+样张脚本扩展(两版画布出口、整屏四项与战斗表现五项机检 30/30 全绿、单元套件 18 checks 全绿)。**方向稿目检先行**(art-bible §1.4 闸形)——机检已全绿,气质终审通过才算样张整体过闸;通过前终稿资产与动画设计不启动。登记不阻塞(2026-10-12 真人检测立规);读数与论证详见实现报告(git 提交 3b66d1a6 一线可考)。 | # | 目检项 | 判据 | 材料 | diff --git a/待办清单.md b/待办清单.md index bcc2bd97..d9887e14 100644 --- a/待办清单.md +++ b/待办清单.md @@ -170,7 +170,7 @@ #### 站桩帧动画——Alpha 定版闸前完成(2026-10-06 登记,来源=审计发现) -**实况**:审计报告 P0-3——定稿立绘零帧动画;现行动画全部程序化(死亡碎裂/受击闪白/意图徽标弹入=程序 tween,非帧资产)。用户裁定**动画是较往后的优化,Alpha 版本定版时间点之前完成即可**——当前 Alpha 开发期达成「看上去可游玩」即可。路线未拍板,审计报告三路线供重启时裁决:①**拆件驱动**(报告推荐:定稿立绘切部件〔头/躯干/臂/腿/武器〕+ 节点层级 tween 驱动——零身份漂移、帧数无限、内存小;项目已有先例=shovel_swing 三帧 + 程序化死亡碎裂)②AI 关键姿势 + 人工修 + 程序补帧 ③全 AI 逐帧(不推荐:身份漂移/像素抖动)。前置依赖=「立绘→派生形态」管线(原稿像素化后入包、禁再降采样 + 降色 ≤32 + 脚底基线对齐 + 命名合规;显示缩放走 art-bible §8.2 乙类口径=档高÷源高、NEAREST、允许非整数倍)。**2026-11-04 用户裁改排 Beta**:本条移出 Alpha 定版前置清单,Alpha 定版不再以它为闸,路线三案与上述前置管线依赖原样留到 Beta 立项再裁(取代 2026-10-06 登记与 2026-10-13 补裁的「定版前完成最小版/未完成则顺延 Beta」口径;标题「Alpha 定版闸前完成」系登记时点原文,按登记元数据禁改条款保留)。 +**实况**:审计报告 P0-3——定稿立绘零帧动画;现行动画全部程序化(死亡碎裂/受击闪白/意图徽标弹入=程序 tween,非帧资产)。用户裁定**动画是较往后的优化,Alpha 版本定版时间点之前完成即可**——当前 Alpha 开发期达成「看上去可游玩」即可。路线未拍板,审计报告三路线供重启时裁决:①**拆件驱动**(报告推荐:定稿立绘切部件〔头/躯干/臂/腿/武器〕+ 节点层级 tween 驱动——零身份漂移、帧数无限、内存小;项目已有先例=shovel_swing 三帧 + 程序化死亡碎裂)②AI 关键姿势 + 人工修 + 程序补帧 ③全 AI 逐帧(不推荐:身份漂移/像素抖动)。前置依赖=「立绘→派生形态」管线(等比整数缩放 + 降色 ≤32 + 脚底基线对齐 + 命名合规)。**2026-11-04 用户裁改排 Beta**:本条移出 Alpha 定版前置清单,Alpha 定版不再以它为闸,路线三案与上述前置管线依赖原样留到 Beta 立项再裁(取代 2026-10-06 登记与 2026-10-13 补裁的「定版前完成最小版/未完成则顺延 Beta」口径;标题「Alpha 定版闸前完成」系登记时点原文,按登记元数据禁改条款保留)。 **缺口**:还差 Beta 立项批的路线正式拍板(预选拆件驱动)——拍板后立帧数规格与资产工单。 - 相关文档:[art-bible](design/art/art-bible.md) §8.4 帧数规格 · 战斗表现批 @@ -192,13 +192,13 @@ #### 棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计(2026-10-30 登记,来源=会话提及) -**实况**:2026-11-04 讨论定案——形=**环+十字法阵**(外环=圈定,环内十字=钉住此格,四端顶到环内沿连成一体);色=封印黑 `#000000` 纯黑无光效(封印紫在封印黑批已退役、不并存);画法=纯黑实心块面、禁细线(32px 下细线断成碎点)。尺寸定值落 [美术圣经](design/art/art-bible.md) v3.38 §8.2「封印印记源档」(外径 23px/环壁 2.6px/十字臂 3.2px/外沿距格边 ≥4px),形与画法落 §3.2。终稿已同名替换 `game/assets/art/board/seal_mark.png`(32×32)。连带件=战末金蜡封徽记沿用同图(零代码改动);特效两件只做常显。消费面 `tile_palette.gd:19`、`board_view.gd:539`/`:582`、`battle_end_overlay.gd:84`。同批清圣经四处「旗」与 §4.1 旧形前提,[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 同步;圣经 :370/:540 两处「背包旗」属 character-design 命名,未动。 +**实况**:用户口径=封印图标现为扫雷旗形,改法阵或锁链待设计,形未定先做讨论。现件 `game/assets/art/board/seal_mark.png`(32×32 旗形剪影);消费面两处——棋盘封印槽绘制(`tile_palette.gd:19` → `board_view.gd:616`)与战末金蜡封徽记(`battle_end_overlay.gd:65-69` 复用同图);「旗」概念已全库退役(2026-10-18),文档口径仍以旗为形待换代:[美术圣经](design/art/art-bible.md) v3.23 §3.2/§4.1、[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4、[战斗屏 UX](design/gdd/ux/battle.md) :128/:270、[体验验证清单](体验验证清单.md) 第 4 条。 -**缺口**:还差两件:①实机目检(三关封印印记形状可读、与巢穴纹/变异纹带不同格混淆、战末蜡封观感);②施印瞬间特效未实装(清单 §4 两件中的第一件)。 +**缺口**:还差封印印记图形设计讨论(法阵/锁链两案并列,禁代裁),须一并裁五点:①形(与危险格巢穴纹、奖励徽章剪影语言 art-bible §3.1 互辨);②色(法阵带发光/色相与 §4.1 封印黑不变槽冲突,须重登记判据);③尺寸遮挡(32×32 内「数字 > 小人 > 封印印记」栈位不侵入数字字形区);④金蜡封徽记与悬停「封印(印记形)」光标是否同步换;⑤与封印特效两件(施印瞬间/常显形态)合并或分立。裁决写入 art-bible §3.2/§4.1 与两份美术清单后方可出样张(终稿名沿用 `seal_mark.png`)。 **改动面**:文档+美术素材 -**状态**:美术设计中 +**状态**:待美术设计 ### 教学引导 From 372b4fdc31e62809b282d67fd05bf68d79c6d080 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:08:31 +0800 Subject: [PATCH 03/19] =?UTF-8?q?iter51-=E5=8D=A12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/adventure_runner.gd | 66 +-- game/core/battle_fx_host.gd | 16 + game/core/battle_orchestrator.gd | 85 +-- game/core/breakpoint_store.gd | 64 ++- game/core/config_db.gd | 8 +- game/core/damage_controller.gd | 27 +- game/core/minesweeper_rules.gd | 24 + game/core/reward_grant.gd | 14 + game/core/save_manager.gd | 46 +- game/core/settlement/aura_segment.gd | 15 +- game/core/settlement/battle_refresh_flow.gd | 64 ++- game/core/settlement/combat_segment.gd | 81 +-- game/core/settlement/decay_segment.gd | 18 +- game/core/settlement/grant_segment.gd | 183 ++++--- game/core/settlement/round_cycle.gd | 105 ++-- game/core/settlement/verdict_segment.gd | 38 +- game/core/slot_store.gd | 53 +- game/data/schemas/board_gen_table.gd | 7 +- game/data/schemas/difficulty_table.gd | 7 +- game/data/schemas/enemy_table.gd | 7 +- game/tests/run_all.gd | 4 +- .../settlement_wiring_snapshot_test.gd | 501 ++++++++++++++++++ .../settlement_wiring_snapshot_test.gd.uid | 1 + game/tests/unit/core/adventure_feats_test.gd | 6 +- .../unit/core/encapsulation_sweep_test.gd | 110 ++++ .../unit/core/encapsulation_sweep_test.gd.uid | 1 + game/tests/unit/core/settle_segments_test.gd | 36 +- 27 files changed, 1231 insertions(+), 356 deletions(-) create mode 100644 game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd create mode 100644 game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd.uid create mode 100644 game/tests/unit/core/encapsulation_sweep_test.gd create mode 100644 game/tests/unit/core/encapsulation_sweep_test.gd.uid diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 2ee4af3d..1589624f 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -104,7 +104,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: _reset_curio_state() levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数) level_index = 1 - _bp_store._pending_next_level = false + _bp_store.set_pending_next_level(false) _shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动) _treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {} _attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive @@ -112,7 +112,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: _refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池) _loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面) adventure_inventory.reset() # 冒险级库存重置(新局空库存) - _bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后) + _bp_store.set_loadout_seeded(false) # 同调行投行延至首关 bind 时点(库存就位后) adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零) # 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓: # 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕) @@ -154,7 +154,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: summary = {} levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数) level_index = int(bp.get("level_index", 1)) - _bp_store._pending_next_level = false + _bp_store.set_pending_next_level(false) _reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典) _bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS) _bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧) @@ -165,7 +165,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面) _tree_rules = params.get("tree_rules", {}) if params.get("tree_rules", {}) is Dictionary else {} # A/B 面规则键参数字典(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传) # 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典) - var bp_counters = _bp_store._bp_read("adventure_counters", bp) + var bp_counters = _bp_store.bp_read("adventure_counters", bp) if bp_counters is Dictionary: adventure_counters = (bp_counters as Dictionary).duplicate(true) return _resume_pending_or_start(bp) @@ -177,18 +177,18 @@ func _resume_pending_or_start(bp: Dictionary) -> bool: var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary if not bp_pending.is_empty(): - _bp_store._pending_next_level = true + _bp_store.set_pending_next_level(true) _state = State.RUNNING _stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继) return true if not bp_result.is_empty(): - _bp_store._pending_next_level = true + _bp_store.set_pending_next_level(true) _stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件) _state = State.RUNNING return true - _bp_store._resume_bp = bp.duplicate(true) + _bp_store.set_resume_bp(bp.duplicate(true)) # 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory) - _bp_store._loadout_seeded = false + _bp_store.set_loadout_seeded(false) _loadout.set_attunement(_attunement) adventure_inventory.reset() _state = State.RUNNING @@ -198,7 +198,7 @@ func _resume_pending_or_start(bp: Dictionary) -> bool: func _reset_curio_state() -> void: _curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理) _stop.reset_adventure_state() - _bp_store._loadout_seeded = false + _bp_store.set_loadout_seeded(false) _stats.feats = [] adventure_counters = {} @@ -217,7 +217,7 @@ func effective_max_hp() -> int: # 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默) func set_save_manager(sm: SaveManagerCore) -> void: - _bp_store._save_manager = sm + _bp_store.bind_save_manager(sm) # 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取) @@ -232,13 +232,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int: # 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进) func continue_to_next_level() -> bool: - if not _bp_store._pending_next_level: + if not _bp_store.pending_next_level(): push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进") return false if _stop.curio_stop_pending(): push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)") return false - _bp_store._pending_next_level = false + _bp_store.set_pending_next_level(false) _stop.clear_resolved() level_index += 1 return _start_current_level() @@ -297,9 +297,9 @@ func _loadout_on_level_inventory() -> void: var inv = _runner.treasure_inventory if inv == null: return - if not _bp_store._loadout_seeded: - _bp_store._loadout_seeded = true - var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null + if not _bp_store.loadout_seeded(): + _bp_store.set_loadout_seeded(true) + var bp_charges: Variant = _bp_store.bp_read("attunement_charges", _bp_store.resume_bp(), null) if not _bp_store.resume_bp().is_empty() else null if bp_charges is Dictionary: _loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数 else: @@ -322,8 +322,9 @@ func _loadout_on_level_inventory() -> void: # apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因) func _boss_keystone_drop(li: int) -> void: var first := true - if _bp_store._save_manager != null: - first = not _bp_store._save_manager.meta_state.career_state().boss_first_kill.has(str(li)) + var sm_keystone = _bp_store.save_manager() + if sm_keystone != null: + first = not sm_keystone.meta_state.career_state().boss_first_kill.has(str(li)) var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li) var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0)) _journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语 @@ -373,9 +374,9 @@ func _start_current_level() -> bool: # 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解), # resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。 var resume_state: Dictionary[Variant, Variant] = {} - if not _bp_store._resume_bp.is_empty(): - resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)), - "visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array} + if not _bp_store.resume_bp().is_empty(): + resume_state = {"node_id": int(_bp_store.bp_read("node_id", _bp_store.resume_bp(), -1)), + "visited_ids": _bp_store.bp_read("visited_ids", _bp_store.resume_bp(), []) as Array} # 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好); # 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。 # defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在 @@ -385,7 +386,7 @@ func _start_current_level() -> bool: if ok: _emit_level_started() _present_level_entry(resume_state) - _bp_store._resume_bp = {} + _bp_store.clear_resume_bp() return ok @@ -409,7 +410,7 @@ func _present_level_entry(resume_state: Dictionary) -> void: if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0: _runner.present_entry_choice() else: - _runner.restore_treasure_items(_bp_store._bp_read("aux_items", _bp_store._resume_bp, []) as Array) + _runner.restore_treasure_items(_bp_store.bp_read("aux_items", _bp_store.resume_bp(), []) as Array) _runner.present_resumed_choice() @@ -418,7 +419,7 @@ func _on_level_finished(payload: Dictionary) -> void: if state != "running": push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state) return - _bp_store._last_level_payload = payload.duplicate(true) + _bp_store.set_last_level_payload(payload.duplicate(true)) var result := String(payload.get("result", "")) if result == "defeat": _stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径) @@ -427,7 +428,7 @@ func _on_level_finished(payload: Dictionary) -> void: if result != "victory": push_error("[冒险结构] 关卡结束结果出域: %s" % result) return - _bp_store._last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面) + _bp_store.set_last_treasure_cleared(int(payload.get("aux_cleared", 0))) # additive(末关 summary 取数面) # Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone) _boss_keystone_drop(level_index) if level_index < levels_total: @@ -435,7 +436,7 @@ func _on_level_finished(payload: Dictionary) -> void: #(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位); # 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算, # 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp) - _bp_store._pending_next_level = true + _bp_store.set_pending_next_level(true) var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true) stop_base["gp"] = _wallet.gp _stop.open_level_clear(stop_base) @@ -453,7 +454,7 @@ func _settle(result: String) -> void: "stopped_at": 0 if result == "completed" else level_index, "keystone_total": _journal.keystone_gained(), "master_seed": master_seed, - "aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷) + "aux_cleared_last": _bp_store.last_treasure_cleared(), # additive 扩签(末关清空反馈;非末关走 bonus 载荷) "keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源) "vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档) "pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面) @@ -461,19 +462,20 @@ func _settle(result: String) -> void: } # 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源; # 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知) - if result == "failed" and not _bp_store._last_level_payload.is_empty(): + if result == "failed" and not _bp_store.last_level_payload().is_empty(): summary["defeat"] = { "level_index": level_index, - "node_id": int(_bp_store._last_level_payload.get("node_id", -1)), - "node_type": String(_bp_store._last_level_payload.get("node_type", "")), + "node_id": int(_bp_store.last_level_payload().get("node_id", -1)), + "node_type": String(_bp_store.last_level_payload().get("node_type", "")), } if _runner != null: summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点) _runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现) _reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化) - if _bp_store._save_manager != null: - _bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点) - _bp_store._save_manager.write_to_disk() + var sm_end = _bp_store.save_manager() + if sm_end != null: + sm_end.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点) + sm_end.write_to_disk() _state = State.SETTLING adventure_finished.emit(summary) _state = State.IDLE diff --git a/game/core/battle_fx_host.gd b/game/core/battle_fx_host.gd index d789cd2c..96141af0 100644 --- a/game/core/battle_fx_host.gd +++ b/game/core/battle_fx_host.gd @@ -89,6 +89,22 @@ func counter_add(counter_id: String, value: int, set_form: bool) -> void: EffectEngine.counter_add(counters_ctx, counter_id, value, set_form) +# 新资源类型首见登记(授予段 dig_reveal 地质学家判定面——返回 true=首见并置位;空串短路零登记) +func mark_new_resource_type(ctype: String) -> bool: + if ctype == "": + return false + if _fx_new_resource_types.has(ctype): + return false + _fx_new_resource_types[ctype] = true + return true + + +# bonus_delta 轴累积(授予段血税双向施加面——axis 在册四轴才累积,轴外静默) +func add_bonus_delta(axis: String, delta: int) -> void: + if _fx_bonus_delta.has(axis): + _fx_bonus_delta[axis] += delta + + # 业务发射事实面(闭面四键——三 when 事实 {dig_streak 轮内槽/hp_ratio 主角 # 血量比例算式逐字承 damage_controller 原式}∪ 两 rider 运行态读数 {facts_inject 现值}; # 其余事实一律 null——battle_index/round_index/seals_count/nest_count 禁进,扩面=行为变化) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 9f772995..0aba641a 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -65,12 +65,6 @@ var nest_preview_count := -1 # 巢穴预告读数口(board_insta # 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动) -# 主角攻击基值派生读(enc 携带 hero_atk 键受理后只读——E4 收口语义不变:表值运行期不可变, -# 派生值恒等;「读面即引用、无第二账」同款先例) -func _hero_atk() -> int: - return int(_facts.encounter.get("hero_atk", int(db.combat.hero.atk))) - - # 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter 受理 assign 后全生命期可读; # null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账) func _journal() -> GainJournal: @@ -218,7 +212,7 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: _facts = BattleFacts.new() # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4) _reward_economy = RewardEconomy.new(db, rng) _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线) - _damage = DamageController.new(db, rng, _enemy_system, _status_effects) + _damage = DamageController.new(db, rng, _enemy_system, _status_effects, _facts) _damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄) _fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper, "action_economy": action_economy, "damage": _damage, "emit_cb": _emit_row, "facts_cb": _fx_host_fact}) @@ -232,9 +226,19 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: minesweeper.seal_judged.connect(_on_seal_judged) minesweeper.round_end_board.connect(_on_round_end) action_economy.round_end_stopped.connect(_on_round_end) - # 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕) - # 结算五段接线随轮循环驱动件迁驻(round_cycle.gd——段件持有/段序/重入闸/摇号单点;构造序禁环不变) - _rounds = RoundCycle.new(self, _fx_host, _reward_grant) + # 结算五段接线(引用束注入——BoardInstaller 先例 architecture.md:124:六子对象+既有公开协作件 + # 引用+两公开读数引用+七编排器方法回调+两公开读投影,20 键封闭束;段类各取所需, + # 段类对编排器零反向引用——跨件单向接线,注入 Callable 亦属接线面) + var seg_bundle: Dictionary[Variant, Variant] = { + "facts": _facts, "units": _enemy_system, "damage": _damage, "status_effects": _status_effects, + "reward_economy": _reward_economy, "reward_grant": _reward_grant, "fx_host": _fx_host, + "minesweeper": minesweeper, "db": db, "action_economy": action_economy, "rng": rng, + "emit_row_cb": _emit_row, "fatal_cb": _fatal, "install_cb": _install_and_reset, + "battle_over_cb": _battle_over, "exit_distribution_cb": _exit_distribution, + "emit_snapshot_cb": _emit_settlement_snapshot, "emit_composition_cb": _emit_composition_effects, + "attune_param_sum_cb": attune_param_sum, "perfect_sweep_cb": perfect_sweep_earned, + } + _rounds = RoundCycle.new(seg_bundle) # ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)---- @@ -259,7 +263,7 @@ func begin_battle(enc: Dictionary) -> bool: if not action_economy.notify_round_start(): _fatal("行动经济 首轮 notify_round_start 被拒") return false - _rounds.roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读) + _rounds.roll_round_actions(round_index) # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读) round_started.emit(round_index) return true @@ -278,8 +282,8 @@ func _begin_battle_accept(enc: Dictionary) -> bool: _fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))]) return false # E4 收口:加成经 _facts.encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径; - # _hero_atk 派生读=受理后不变量,无快照成员) - if not _enemy_system.spawn({"level_index": int(enc.level_index), "node_depth": int(enc.node_depth), "hero_hp": int(enc.hero_hp), "unit_count_overrides": dice[0], "hero_atk_override": _hero_atk(), "hero_max_hp_override": int(enc.get("hero_max_hp", -1)), "enemy_atk_overrides": dice[1], "hero_attack_type": String(enc.get("attack_type", "")), "hero_armor_type": String(enc.get("armor_type", "")), "row_kind": String(enc.get("node_type", "battle"))}): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss) + # _damage.hero_atk() 派生读=受理后不变量,无快照成员) + if not _enemy_system.spawn({"level_index": int(enc.level_index), "node_depth": int(enc.node_depth), "hero_hp": int(enc.hero_hp), "unit_count_overrides": dice[0], "hero_atk_override": _damage.hero_atk(), "hero_max_hp_override": int(enc.get("hero_max_hp", -1)), "enemy_atk_overrides": dice[1], "hero_attack_type": String(enc.get("attack_type", "")), "hero_armor_type": String(enc.get("armor_type", "")), "row_kind": String(enc.get("node_type", "battle"))}): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss) _fatal("敌人系统 spawn 被拒") return false return true @@ -444,54 +448,7 @@ func _on_seal_judged(evt: Dictionary) -> void: func _on_round_end(_payload: Dictionary) -> void: _facts.path = String(_payload.get("path_tag", "")) - _rounds.settle_if_due() - - - - -func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary: - if positive and pos_m <= 0: - return snap - if not positive and neg_m <= 0: - return snap - var out: Dictionary[Variant, Variant] = 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 - 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[Variant] = [] - for vid_v in vids: - var vid := String(vid_v) - var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(vid) - if String(vrow.get("effect", "")) == "flat_damage": - out.append(vid) - return out - - -# 邻接伤害型变异收集(terrain_kind fact 取数面——seal_hit 发射点用:封印命中相邻八格的伤害型变异集; -# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集) -func _harmful_adjacent(cell: Vector2i) -> Array: - var out: Array[Variant] = [] - 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[Variant] = [] - for h in hits: - if String((h as Dictionary).get("kind", "")) == kind: - out.append(h) - return out + _rounds.settle_if_due(battle_result, round_index) # aux 解锁门控转发(LevelRunner 注入;E1/E3 封装:外部经公有面,禁直读 _reward_economy) @@ -586,13 +543,13 @@ func _exit_distribution() -> void: action_economy.notify_round_start() _facts.reset_round() _facts.refresh_used = 0 - _rounds.roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道) + _rounds.roll_round_actions(round_index) # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道) round_started.emit(round_index) # 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow) func can_refresh() -> bool: - return BattleRefreshFlow.can_refresh(self) + return _rounds.refresh_flow().can_refresh(battle_result) # 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。 @@ -600,7 +557,7 @@ func can_refresh() -> bool: # 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow # (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。 func player_request_refresh() -> bool: - return BattleRefreshFlow.execute(self) + return _rounds.refresh_flow().execute(battle_result, round_index) func _battle_over(result: String) -> void: diff --git a/game/core/breakpoint_store.gd b/game/core/breakpoint_store.gd index 61b10d21..699a5367 100644 --- a/game/core/breakpoint_store.gd +++ b/game/core/breakpoint_store.gd @@ -157,7 +157,7 @@ func restore_curio_state(bp: Dictionary) -> void: _curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:)随快照还原 # 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径; # relic 令牌 feat_id 随还原) - var bp_deck = _bp_read("bonus_deck", bp) + var bp_deck = bp_read("bonus_deck", bp) if bp_deck is Array: var restored: Array[Variant] = [] for t in (bp_deck as Array): @@ -192,8 +192,9 @@ func restore_stop_bank_fragments(bp: Dictionary, params: Dictionary) -> void: # 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。 -# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。 -func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant: +# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口; +# 公开化(5.1 封装修复——宿主读侧同口,_bp_read 更名非并存) +func bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant: if not BREAKPOINT_FIELDS.has(key): push_error("[冒险结构] 断点字段未登记(读侧): %s" % key) var v: Variant = bp.get(key, fallback) @@ -216,3 +217,60 @@ func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary return maxi(0, int(bp.get(key, int(params.get(key, fallback))))) _: return int(bp.get(key, fallback)) + + +# ---- 断点域六成员公开读写面(5.1 封装修复——宿主私取清零:方法体一律一行转发自有成员,零逻辑增量)---- + +# 断点承载注入(宿主构造后接线——save_manager 后绑) +func bind_save_manager(sm) -> void: + _save_manager = sm + + +# 断点承载读投影(可 null——模拟/冒烟缺省态) +func save_manager() -> Variant: + return _save_manager + + +func set_pending_next_level(v: bool) -> void: + _pending_next_level = v + + +func pending_next_level() -> bool: + return _pending_next_level + + +func set_loadout_seeded(v: bool) -> void: + _loadout_seeded = v + + +func loadout_seeded() -> bool: + return _loadout_seeded + + +func set_resume_bp(bp: Dictionary) -> void: + _resume_bp = bp + + +func clear_resume_bp() -> void: + _resume_bp = {} + + +# 续玩快照读(返引用零拷贝——宿主读侧只读) +func resume_bp() -> Dictionary: + return _resume_bp + + +func set_last_level_payload(payload: Dictionary) -> void: + _last_level_payload = payload + + +func last_level_payload() -> Dictionary: + return _last_level_payload + + +func set_last_treasure_cleared(v: int) -> void: + _last_treasure_cleared = v + + +func last_treasure_cleared() -> int: + return _last_treasure_cleared diff --git a/game/core/config_db.gd b/game/core/config_db.gd index de14a9b3..996e062c 100644 --- a/game/core/config_db.gd +++ b/game/core/config_db.gd @@ -153,10 +153,10 @@ func _assign_loaded(loaded: Dictionary) -> void: presentation.camp_scene_layout = loaded.get("camp_scene_layout") for problem: String in presentation.camp_scene_layout.validate(): _fatal("camp_scene_layout", problem) # 布局几何红线(命中区越画面/相交/侵顶栏)=数据致命 - # resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移) - combat.board_gen._resolver = _resolver - combat.enemy._resolver = _resolver - run.difficulty._resolver = _resolver + # resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移;5.1 封装修复改经 bind_resolver 公开挂载口) + combat.board_gen.bind_resolver(_resolver) + combat.enemy.bind_resolver(_resolver) + run.difficulty.bind_resolver(_resolver) # 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体: diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index e095f8f4..4223ef42 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -44,16 +44,33 @@ var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle # 效果端口件(组装期单点注入——host _init bind_port):发射/计数器/事实/揭示/AC 全经端口路由, # 缺省 null=未绑(与原 _fx==null 零扰动守卫同语义——单测直构不接端口的夹具零扰动照跑) var _port: EffectPort = null +var facts: BattleFacts # 战斗事实件引用(构造注入——hero_atk 派生读消费面) -func _init(db_ref, rng_ref, units_ref, effects_ref) -> void: +func _init(db_ref, rng_ref, units_ref, effects_ref, facts_ref) -> void: db = db_ref rng = rng_ref units = units_ref effects = effects_ref + facts = facts_ref units.unit_died.connect(_on_unit_died) +# 主角攻击基值派生读(enc 携带 hero_atk 键受理后只读——E4 收口语义不变:表值运行期不可变, +# 派生值恒等;自编排器归位(5.1 封装修复——伤害域原语),「读面即引用、无第二账」同款先例) +func hero_atk() -> int: + return int(facts.encounter.get("hero_atk", int(db.combat.hero.atk))) + + +# 效果命中过滤(按效果类型取命中子集——发射点分桶施加面;自编排器归位) +static func hits_of(hits: Array, kind: String) -> Array: + var out: Array[Variant] = [] + for h in hits: + if String((h as Dictionary).get("kind", "")) == kind: + out.append(h) + return out + + # 主角护甲等级合流入参(战斗编排器 begin_battle 调用——encounter.hero_armor_class;缺参/-1 = 表值兜底) func set_hero_ac_override(v: int) -> void: _hero_ac_override = v @@ -423,7 +440,7 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary # 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型—— # 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output # 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。 -# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 _emit_hero_damage_taken(发射在本函数—— +# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 emit_hero_damage_taken(发射在本函数—— # 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。 func _resolve_enemy_skill(t: Dictionary) -> void: var hero: Dictionary[Variant, Variant] = units.hero_unit() @@ -432,7 +449,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void: # damage_taken 发射(kind=enemy_skill;shield 减伤订阅面:加法族合计 value×per 计数器层值, # 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/ # counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 damage_taken 载荷携带) - var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index)) + var dt_res := emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index)) total = int(dt_res["total"]) units.apply_damage(hero, total) var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)), @@ -468,7 +485,7 @@ func _enemy_roll(t: Dictionary) -> Dictionary: # 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次; # 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。 # payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。 -func _emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary: +func emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary: var hu: Dictionary[Variant, Variant] = units.hero_unit() var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source, "cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))} @@ -544,7 +561,7 @@ func _resolve_board_blast(t: Dictionary) -> void: func _resolve_board_flat_hero(t: Dictionary) -> void: var dealt: int = int(t.base_value) if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)): - var res_flat := _emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index)) + var res_flat := emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index)) dealt = int(res_flat["total"]) dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限) units.apply_damage(units.hero_unit(), dealt) diff --git a/game/core/minesweeper_rules.gd b/game/core/minesweeper_rules.gd index c6c4b518..531b7936 100644 --- a/game/core/minesweeper_rules.gd +++ b/game/core/minesweeper_rules.gd @@ -53,6 +53,30 @@ func current_board() -> BoardData: return _board +# 伤害型变异过滤(terrain_kind fact 取数面——variation 表 effect=flat_damage 判据; +# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型(hide_number 族)永不出现在本集。自编排器归位) +func harmful_variants(vids: Array) -> Array: + var out: Array[Variant] = [] + for vid_v in vids: + var vid := String(vid_v) + var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(vid) + if String(vrow.get("effect", "")) == "flat_damage": + out.append(vid) + return out + + +# 邻接伤害型变异收集(terrain_kind fact 取数面——seal_hit 发射点用:封印命中相邻八格的伤害型变异集; +# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集。自编排器归位) +func harmful_adjacent(cell: Vector2i) -> Array: + var out: Array[Variant] = [] + var board: BoardData = current_board() + if board == null: + return out + for n in board.neighbors8(cell): + out.append_array(harmful_variants(board.variant_ids(n))) + return out + + # 已开格记录查询面(cell → {kind, digit};未开 = {})——呈现层渲染消费面(board_view 徽章/四格态) func reveal_record(c: Vector2i) -> Dictionary: return _revealed.get(c, {}) diff --git a/game/core/reward_grant.gd b/game/core/reward_grant.gd index 0e468f10..db957961 100644 --- a/game/core/reward_grant.gd +++ b/game/core/reward_grant.gd @@ -67,6 +67,20 @@ func reset_battle() -> void: defer.queue = [] +# 奖励乘区调整(yield_mult 命中聚合——自编排器归位〔5.1 封装修复·奖励域原语〕:power 按 +# pos/neg 乘区百分比放大、下限 1;正负两态由参分;零命中/零乘区原快照直返) +func feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary: + if positive and pos_m <= 0: + return snap + if not positive and neg_m <= 0: + return snap + var out: Dictionary[Variant, Variant] = 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 + out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0)))) + return out + + # 揭示奖励兑付(BattleFxHost.flush_pending_reveals 的兑付回调面——授予面收编: # snapshot 查询与极性路由同在本件,fx_host 对授予面零强引用) func reveal_redeem(cell: Vector2i, ri: int) -> void: diff --git a/game/core/save_manager.gd b/game/core/save_manager.gd index a4fabc6a..8fb980e3 100644 --- a/game/core/save_manager.gd +++ b/game/core/save_manager.gd @@ -48,7 +48,7 @@ func _init(path: String = "") -> void: # 无激活态(注入路径空,一切写盘路径拒绝并返回 false);玩家侧经选档界面显式进档/新档后才可写。 # 旧隐式单档一次性迁入槽 1(Q1 甲,sv12)在本口执行。 func _ready() -> void: - slots._migrate_legacy_save(LEGACY_SAVE_PATH, slots.slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX) + slots.migrate_legacy_save(LEGACY_SAVE_PATH, slots.slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX) # 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约: @@ -63,10 +63,10 @@ func register_migration(from_version: int, fn: Callable) -> void: # 损坏兜底:原文件改名 .corrupt.bak(已存在则覆盖)+ push_warning + 默认数据继续——不静默丢档。 func load_from_disk() -> bool: _read_only = false - if not FileAccess.file_exists(slots._path): + if not FileAccess.file_exists(slots.active_path()): _data = _default_data() # 新档合法态 return true - var f := FileAccess.open(slots._path, FileAccess.READ) + var f := FileAccess.open(slots.active_path(), FileAccess.READ) if f == null: return _corrupt_fallback("读打开失败(%s)" % error_string(FileAccess.get_open_error())) return _load_parsed(f) @@ -102,21 +102,21 @@ func _load_parsed(f: FileAccess) -> bool: # 无激活态一律拒绝并返回 false(sv3:启动态不隐式激活,盘面零触碰); # 高版本拒读态一律拒绝(契约 4 禁写);写失败返回 false。 func write_to_disk() -> bool: - if slots._path == "": + if slots.active_path() == "": push_error("[存档序列化] 写盘拒绝——无激活存档位(启动态不隐式激活;经选档进档/新档后落盘)") return false if _read_only: push_error("[存档序列化] 写盘拒绝——档案版本高于当前(拒读禁写态)") return false - var tmp := slots._path + ".tmp" + var tmp := slots.active_path() + ".tmp" var f := FileAccess.open(tmp, FileAccess.WRITE) if f == null: push_error("[存档序列化] 临时文件打开失败:%s(%s)" % [tmp, error_string(FileAccess.get_open_error())]) return false f.store_string(JSON.stringify(_data, "\t", true)) f.close() - if not _rename_overwrite(tmp, slots._path): - push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, slots._path]) + if not rename_overwrite(tmp, slots.active_path()): + push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, slots.active_path()]) return false return true @@ -214,7 +214,7 @@ func _peek_migrated(parsed: Variant, fail: Array) -> Variant: ## 摘要窥视内核(只读推演单点——SlotStore 两 peek 方法经槽路径派生委托至此; ## 迁移链在此只读推演——生产迁移 Callable 为纯函数,零盘面副作用) -func _peek_summary_at(path: String) -> Dictionary: +func peek_summary_at(path: String) -> Dictionary: var out := {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} if not FileAccess.file_exists(path): out["readable"] = true # 空槽=可读零值合法态 @@ -275,15 +275,35 @@ func _default_data() -> Dictionary[String, Variant]: } -# 原地重置默认态(SlotStore 槽位原语的协作口——激活/建删/换注回默认数据) -func _reset_to_default() -> void: +# 原地重置默认态(SlotStore 槽位原语的协作口——激活/建删/换注回默认数据; +# 公开化〔5.1 封装修复——SlotStore 反向私取承接,_reset_to_default 更名非并存〕) +func reset_to_default() -> void: _data = _default_data() +# 禁写位公开面(SlotStore 激活/换注三态恢复消费——一行转发自有成员,零逻辑增量) +func set_read_only(v: bool) -> void: + _read_only = v + + +func read_only() -> bool: + return _read_only + + +# 数据快照还原公开面(SlotStore 激活/新建失败回退三态恢复消费——零拷贝语义:snapshot 返 _data +# 本引用、restore 直还引用,行为与原 _data 直读写逐位等价) +func data_snapshot() -> Dictionary[String, Variant]: + return _data + + +func restore_data(data: Dictionary[String, Variant]) -> void: + _data = data + + # 损坏兜底:坏档改名 .corrupt.bak(已存在则覆盖)→ 默认数据继续。不静默丢档(警告留痕)、不 crash。 func _corrupt_fallback(reason: String) -> bool: - var bak := slots._path + ".corrupt.bak" - if not _rename_overwrite(slots._path, bak): + var bak := slots.active_path() + ".corrupt.bak" + if not rename_overwrite(slots.active_path(), bak): push_warning("[存档序列化] 坏档备份改名失败(原文件保留原地):%s——%s" % [bak, reason]) else: push_warning("[存档序列化] 检测到损坏档案,已备份为 %s——%s(用默认新档继续)" % [bak, reason]) @@ -314,7 +334,7 @@ func _normalize(data: Dictionary) -> Dictionary[String, Variant]: # 覆盖式改名(Windows 原生 rename 目标已存在时会失败——先删后改,显式覆盖语义;IO 原语单点—— # SlotStore 迁移流经本件调用) -func _rename_overwrite(from: String, to: String) -> bool: +func rename_overwrite(from: String, to: String) -> bool: var dir := DirAccess.open(from.get_base_dir()) if dir == null: return false diff --git a/game/core/settlement/aura_segment.gd b/game/core/settlement/aura_segment.gd index 301457c0..8f683e97 100644 --- a/game/core/settlement/aura_segment.gd +++ b/game/core/settlement/aura_segment.gd @@ -2,15 +2,18 @@ class_name AuraSegment extends RefCounted # ② 增益/减益段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 # 职责:续存实例组合行(本结算新授予的已在授予段逐 grant 出行——增益状态 义务⑦)。 -# 行发射归宿主编排器(oc)——零算法改动(🔴 零行为变更契约)。 +# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 1 键 +# (emit_composition_cb)解包;组合行发射经回调回宿主保留面——对编排器零引用。 +# 零算法改动(🔴 零行为变更契约)。 -var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主 +var emit_composition_cb: Callable # 宿主 _emit_composition_effects——续存实例组合行发射(保留面) -func _init(host) -> void: - oc = host +func _init(bundle: Dictionary) -> void: + assert(bundle.has("emit_composition_cb"), "[增益减益段] 引用束缺键: emit_composition_cb") + emit_composition_cb = bundle.emit_composition_cb func run(ri: int) -> void: - oc._emit_composition_effects("hero", ri) - oc._emit_composition_effects("enemy", ri) + emit_composition_cb.call("hero", ri) + emit_composition_cb.call("enemy", ri) diff --git a/game/core/settlement/battle_refresh_flow.gd b/game/core/settlement/battle_refresh_flow.gd index 0f6a30e8..dc7b4326 100644 --- a/game/core/settlement/battle_refresh_flow.gd +++ b/game/core/settlement/battle_refresh_flow.gd @@ -2,42 +2,72 @@ class_name BattleRefreshFlow extends RefCounted ## 换图刷新流程件(迭代 46 卡A T3/T4 自编排器拆出):刷新可用性门、折价扣费、换图执行与成功事实登记。 ## 拆分边界(需求卡片 C13):本件只负责可用性、扣费、换图与成功事实登记——七件效果的算法留在 -## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→本件→行动经济/装板壳/授予段。 +## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→RoundCycle.refresh_flow()→本件 +## →行动经济/装板壳/授予段(编排器入口与单行转发形态不变)。 +## 耦合边界(5.1 封装修复换代):去 static 改实例——构造收 RoundCycle 复制的引用束,断言 8 键 +## (facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg)解包同名成员; +## 变值标量(battle_result/round_index)走调用参数现值交付;授予段引用直接交换(段间引用, +## 不再穿编排器链式取段)——对编排器零引用。 ## 反自动刷新(C11):只经玩家入口发起,禁 RAP 满自动刷——编排器公开面单行转发,无自动调用点。 ## 成功序钉死(T4):折价扣 RAP→refresh_used+1→旧资源退役→旧板退役→装新板→授予段成功事实发射; ## 装板失败返回 false 且零效果——成功事实只在新板就位后发射。 +## 随编排器每战新建同灭(RoundCycle 持有)。 + +var facts: BattleFacts # 战斗事实件(refresh_used 受理计数) +var db: ConfigDb +var action_economy: ActionEconomy +var fx_host: BattleFxHost # 效果宿主件(refresh_request 发射面) +var reward_economy: RewardEconomy # 地图战斗资源(旧批退役面) +var minesweeper: MinesweeperRules # 排雷玩法(旧板退役面) +var install_cb: Callable # 宿主 _install_and_reset——装板受理薄壳(保留面) +var grant_seg = null # GrantSegment 段间引用(成功事实发射——直接交接零链式) + + +func _init(bundle: Dictionary) -> void: + for k in ["facts", "db", "action_economy", "fx_host", "reward_economy", "minesweeper", "install_cb", "grant_seg"]: + assert(bundle.has(k), "[换图刷新流程件] 引用束缺键: %s" % k) + facts = bundle.facts + db = bundle.db + action_economy = bundle.action_economy + fx_host = bundle.fx_host + reward_economy = bundle.reward_economy + minesweeper = bundle.minesweeper + install_cb = bundle.install_cb + grant_seg = bundle.grant_seg ## 折价成本读数(refresh_request 命中省力周转 apply_discount——只认 kind==apply_discount 且 ## mode==refresh,取声明序第一命中,floor(base×cost_mult/100);无命中=表基线逐位不变。 ## can_refresh 与 execute 同源取数〔V3——can 与执行同源判断〕;折扣只改变门槛与扣费, ## 不改变拒绝判据〔C2——每轮上限与相位判据照旧〕) -static func _effective_refresh_cost(oc: BattleOrchestrator) -> int: - var base := int(oc.db.run.action_economy.rap_refresh_cost) - for h in oc.fx_host().fx_emit("refresh_request", {"mode": "refresh", "base": base}): +func _effective_refresh_cost() -> int: + var base := int(db.run.action_economy.rap_refresh_cost) + for h in fx_host.fx_emit("refresh_request", {"mode": "refresh", "base": base}): if String(h.get("kind", "")) == "apply_discount" and String(h.get("mode", "")) == "refresh": return int(floor(float(base) * float(int(h.get("cost_mult", 100))) / 100.0)) return base ## 可用性门(HUD 刷新按钮同源判据):战斗未终局、每轮上限未满、Active 相位、RAP 抵折后价。 -static func can_refresh(oc: BattleOrchestrator) -> bool: - return oc.battle_result == "" \ - and oc._facts.refresh_used < int(oc.db.run.action_economy.refresh_cap_per_round) \ - and oc.action_economy.phase == oc.action_economy.PHASE_ACTIVE \ - and oc.action_economy.rap >= _effective_refresh_cost(oc) +## battle_result=调用参数现值交付。 +func can_refresh(battle_result: String) -> bool: + return battle_result == "" \ + and facts.refresh_used < int(db.run.action_economy.refresh_cap_per_round) \ + and action_economy.phase == action_economy.PHASE_ACTIVE \ + and action_economy.rap >= _effective_refresh_cost() ## 执行(玩家请求刷新唯一通道体):扣费受理后继续;扣费拒绝/门拒零效果零事实。 -static func execute(oc: BattleOrchestrator) -> bool: - if not can_refresh(oc): +## battle_result/round_index=调用参数现值交付。 +func execute(battle_result: String, round_index: int) -> bool: + if not can_refresh(battle_result): return false - if not oc.action_economy.request_refresh(_effective_refresh_cost(oc)): + if not action_economy.request_refresh(_effective_refresh_cost()): return false - oc._facts.refresh_used += 1 - oc._reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提) - oc.minesweeper.retire_board(true) # 旧板退役(轮内通道) - if not oc._install_and_reset(true): # 当场换图——失败即编排违例 fail-fast(零成功事实) + facts.refresh_used += 1 + reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提) + minesweeper.retire_board(true) # 旧板退役(轮内通道) + if not bool(install_cb.call(true)): # 当场换图——失败即编排违例 fail-fast(零成功事实) return false - oc._rounds._grant_seg.apply_board_refreshed(oc.round_index) # 成功事实:新板就位后一次发射 + grant_seg.apply_board_refreshed(round_index) # 成功事实:新板就位后一次发射 return true diff --git a/game/core/settlement/combat_segment.gd b/game/core/settlement/combat_segment.gd index baa2d976..15918f9f 100644 --- a/game/core/settlement/combat_segment.gd +++ b/game/core/settlement/combat_segment.gd @@ -4,19 +4,38 @@ extends RefCounted # 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 主角先手 # + 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。 # 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。 -# 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)—— +# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 10 键 +# (facts/units/damage/status_effects/fx_host/minesweeper/db/emit_row_cb/fatal_cb/reward_grant) +# 解包同名成员;行发射经 emit_row_cb 回宿主、fatal 经 fatal_cb 回宿主——对编排器零引用。 # 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。 # run 编排骨架+三私有族(_hero_strike/_enemy_actions/_board_deferred_settle);段序逐字。 -var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) -var fx_host: BattleFxHost # 效果引擎宿主件(三引用构造——本轮无消费面,构造形与授予段同形) -var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方) +var facts: BattleFacts # 战斗事实件(轮缓冲 streak 取数面) +var units: UnitContainer # 单位容器(目标解析/存活查询) +var damage: DamageController # 伤害控制器(开单/提交面) +var status_effects: StatusContainer # 状态容器(增益/妨害施加面) +var fx_host: BattleFxHost # 效果引擎宿主件(facts_inject 写面) +var minesweeper: MinesweeperRules # 排雷玩法(束形统一交付面——卡面 10 键分配) +var db: ConfigDb +var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形) +var fatal_cb: Callable # 宿主 _fatal——battle_result 唯一写点留宿主 +var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方) -func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void: - oc = host - fx_host = fx_host_ref - reward_grant = reward_grant_ref +func _init(bundle: Dictionary) -> void: + for k in ["facts", "units", "damage", "status_effects", "fx_host", "minesweeper", "db", + "emit_row_cb", "fatal_cb", "reward_grant"]: + assert(bundle.has(k), "[战斗段] 引用束缺键: %s" % k) + facts = bundle.facts + units = bundle.units + damage = bundle.damage + status_effects = bundle.status_effects + fx_host = bundle.fx_host + minesweeper = bundle.minesweeper + db = bundle.db + emit_row_cb = bundle.emit_row_cb + fatal_cb = bundle.fatal_cb + reward_grant = bundle.reward_grant func run(ri: int) -> void: @@ -26,12 +45,12 @@ func run(ri: int) -> void: # 同一次 _settle 内,facts 已定。 var round_safe := 0 var round_mine := false - for evt in oc._facts.reveals: + for evt in facts.reveals: if evt.kind == "safe": round_safe += 1 elif evt.kind == "mine": round_mine = true - oc.fx_host().facts_inject["dig_streak"] = 0 if round_mine else round_safe + fx_host.facts_inject["dig_streak"] = 0 if round_mine else round_safe _hero_strike(ri) _enemy_actions(ri) _board_deferred_settle(ri) @@ -46,26 +65,26 @@ func run(ri: int) -> void: # 护甲等级掷点:基础分量吃掷点判定(含暴击);棋盘来源伤害不掷不折; # 任何伤害落地 ≥ damage_floor(全局 max(1,·) 适用所有来源,下限在控制器管线单点收口)。 func _hero_strike(ri: int) -> void: - var hero_alive: bool = int(oc._enemy_system.hero_unit().get("hp", 0)) > 0 - var hero_target = oc._enemy_system.default_target() + var hero_alive: bool = int(units.hero_unit().get("hp", 0)) > 0 + var hero_target = units.default_target() if hero_alive and hero_target != null: - var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击", - {"channel": "combat", "base_value": oc._hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE, + var ht := damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击", + {"channel": "combat", "base_value": damage.hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": Vector2i(-1, -1), - "attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))}) - oc._damage.submit(ht) + "attack_type": String(units.hero_unit().get("attack_type", "normal"))}) + damage.submit(ht) # 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。 # 死亡即不动手:摇定后、兑现前死亡的敌不在存活序中——本拍技能作废,不 commit、不进冷却。 func _enemy_actions(ri: int) -> void: - for u in oc._enemy_system.query_alive("enemy"): - if int(oc._enemy_system.hero_unit().hp) <= 0: + for u in units.query_alive("enemy"): + if int(units.hero_unit().hp) <= 0: break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板) - var skill_id := oc._enemy_system.pending_action(u) - var srow: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(skill_id) + var skill_id := units.pending_action(u) + var srow: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(skill_id) if srow.is_empty(): - oc._fatal("敌实例待发动作悬空: %s" % skill_id) + fatal_cb.call("敌实例待发动作悬空: %s" % skill_id) continue if String(srow.type) == "buff" or String(srow.type) == "debuff": # 增益/妨害技 = 0 伤害施放行:不掷不折;状态授予即数值兑现,技能冷却与增益持续两计时器分账。 @@ -73,9 +92,9 @@ func _enemy_actions(ri: int) -> void: # 敌方增益技只强化施放者自己,不再全体受益);debuff → 主角(off_guard/sapped 同义主角侧)。 var dur: int = int(srow.get("buff_duration", 1)) var owner_key: String = StatusContainer.owner_key_for(u) if String(srow.type) == "buff" else StatusContainer.OWNER_HERO - oc._status_effects.apply_effect(String(srow.buff_ref), owner_key, {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur) - oc._emit_row({"type": "cast", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1)}, ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur}) - oc._enemy_system.commit_action(u) + status_effects.apply_effect(String(srow.buff_ref), owner_key, {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur) + emit_row_cb.call({"type": "cast", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1)}, ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur}) + units.commit_action(u) continue # 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧 # (表值算术零随机),掷点/克制查表(spec 束 attack_type = 敌方攻击类型——迭代 24 N3 轴开列前恒空串不查表)/ @@ -84,27 +103,27 @@ func _enemy_actions(ri: int) -> void: var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0)))) var rider := 0 if srow.has("rider_mult"): - rider = maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult)))) - var et := oc._damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action", + rider = maxi(int(db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult)))) + var et := damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action", "%s %s" % [u.display_name, String(srow.display_name)], {"channel": "combat", "base_value": baseline, "target_rule": DamageController.TARGET_HERO, "ri": ri, "origin_cell": Vector2i(-1, -1), "attack_type": String(u.get("attack_type", "normal")), "rider": rider}) et["source_unit"] = u et["context"] = {"skill_id": skill_id, "baseline": baseline} - oc._damage.submit(et) + damage.submit(et) # 地图伤害兑付(案C:战斗段末——怪物还手后按授予段事件序补提交统一结账; # 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径; # 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段) func _board_deferred_settle(ri: int) -> void: - if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0: + if int(units.hero_unit().get("hp", 0)) <= 0: for t in (reward_grant.defer["queue"] as Array): - oc._emit_row({"type": "reward_ineffective", "source_class": String(t.get("source_class", "")), "origin_cell": t.get("origin_cell", Vector2i(-1, -1))}, ri, + emit_row_cb.call({"type": "reward_ineffective", "source_class": String(t.get("source_class", "")), "origin_cell": t.get("origin_cell", Vector2i(-1, -1))}, ri, "%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"}) else: for t in (reward_grant.defer["queue"] as Array): - oc._damage.submit(t) + damage.submit(t) (reward_grant.defer["queue"] as Array).clear() - oc._damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结) + damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结) diff --git a/game/core/settlement/decay_segment.gd b/game/core/settlement/decay_segment.gd index 5acb1606..07af3e9b 100644 --- a/game/core/settlement/decay_segment.gd +++ b/game/core/settlement/decay_segment.gd @@ -1,16 +1,22 @@ class_name DecaySegment extends RefCounted # ④ 衰减段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 -# 职责:状态衰减恰一次恒执行含末轮——先兑付后衰减。行发射归宿主编排器(oc)—— +# 职责:状态衰减恰一次恒执行含末轮——先兑付后衰减。 +# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 2 键 +# (status_effects/emit_row_cb)解包同名成员;行发射经 emit_row_cb 回宿主——对编排器零引用。 # 零算法改动(🔴 零行为变更契约)。 -var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主 +var status_effects: StatusContainer # 状态容器(衰减推进面) +var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形) -func _init(host) -> void: - oc = host +func _init(bundle: Dictionary) -> void: + for k in ["status_effects", "emit_row_cb"]: + assert(bundle.has(k), "[衰减段] 引用束缺键: %s" % k) + status_effects = bundle.status_effects + emit_row_cb = bundle.emit_row_cb func run(ri: int) -> void: - var k: int = oc._status_effects.decay_round() - oc._emit_row({"type": "decay", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "状态衰减", k, {}) + var k: int = status_effects.decay_round() + emit_row_cb.call({"type": "decay", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "状态衰减", k, {}) diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index e66515a2..1eee08d0 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -2,24 +2,49 @@ class_name GrantSegment extends RefCounted # ① 授予段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 # 职责:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付 + 封印判/军规开单 + round_end 施加 + -# 转存收口 + 揭示兑付 + 段界结账。行发射/开单单点归宿主(oc)——兑付管线归 RewardGrant、 -# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)—— +# 转存收口 + 揭示兑付 + 段界结账。行发射经 emit_row_cb 回宿主——兑付管线归 RewardGrant、 +# 效果施加归 BattleFxHost(段持注入引用直调,零转发壳)—— # 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。 +# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 12 键 +# (facts/units/damage/reward_economy/fx_host/minesweeper/db/action_economy/reward_grant/ +# emit_row_cb/attune_param_sum_cb/perfect_sweep_cb)解包同名成员;对编排器零引用(跨件单向接线, +# 注入 Callable 亦属接线面);变值标量(round_index)走调用参数现值交付。 # run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面 # 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken # 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数); # kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块 # 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。 -var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) -var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶) -var reward_grant: RewardGrant # 奖励授予件(极性路由/行应用/提交面/清图奖励/escrow) +var facts: BattleFacts # 战斗事实件(轮缓冲/封印账/溯源) +var units: UnitContainer # 单位容器(hero_unit 读面) +var damage: DamageController # 伤害控制器(开单/承伤发射面) +var reward_economy: RewardEconomy # 地图战斗资源(grant/query_full_snapshot 消费面) +var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶) +var minesweeper: MinesweeperRules # 排雷玩法(板读取/变异过滤/危险格移除面) +var db: ConfigDb +var action_economy: ActionEconomy +var reward_grant: RewardGrant # 奖励授予件(极性路由/行应用/提交面/清图奖励/escrow) +var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形) +var attune_param_sum_cb: Callable # 宿主 attune_param_sum 读投影——转存率合计读数 +var perfect_sweep_cb: Callable # 宿主 perfect_sweep_earned 读投影——清图奖励第二份判定 -func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void: - oc = host - fx_host = fx_host_ref - reward_grant = reward_grant_ref +func _init(bundle: Dictionary) -> void: + for k in ["facts", "units", "damage", "reward_economy", "fx_host", "minesweeper", "db", + "action_economy", "reward_grant", "emit_row_cb", "attune_param_sum_cb", "perfect_sweep_cb"]: + assert(bundle.has(k), "[授予段] 引用束缺键: %s" % k) + facts = bundle.facts + units = bundle.units + damage = bundle.damage + reward_economy = bundle.reward_economy + fx_host = bundle.fx_host + minesweeper = bundle.minesweeper + db = bundle.db + action_economy = bundle.action_economy + reward_grant = bundle.reward_grant + emit_row_cb = bundle.emit_row_cb + attune_param_sum_cb = bundle.attune_param_sum_cb + perfect_sweep_cb = bundle.perfect_sweep_cb func run(ri: int) -> void: @@ -45,10 +70,10 @@ func apply_board_refreshed(ri: int) -> void: for h in br_hits: if String(h.get("kind", "")) == "grant_gp": reward_grant.escrow += int(h.get("value", 0)) - oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}) + emit_row_cb.call({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}) for h in br_hits: if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast": - var bb_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击", + var bb_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击", {"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE, "ri": ri, "origin_cell": Vector2i(-1, -1)}) reward_grant.defer_ticket(bb_t) @@ -56,7 +81,7 @@ func apply_board_refreshed(ri: int) -> void: # 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付) func _reveal_batch(ri: int) -> void: - for evt in oc._facts.reveals: + for evt in facts.reveals: if not bool(evt.get("resource", false)): continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②) var snap = evt.get("reward_snapshot") @@ -75,16 +100,14 @@ func _reveal_batch(ri: int) -> void: # new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕) func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void: if bool(evt.get("dug_under_seal", false)): - oc._emit_row({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {}) + emit_row_cb.call({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {}) return - var board_b: BoardData = oc.minesweeper.current_board() + var board_b: BoardData = minesweeper.current_board() var ctype := String(snap.get("cell_type", "")) - var is_new_type := ctype != "" and not fx_host._fx_new_resource_types.has(ctype) - if is_new_type: - fx_host._fx_new_resource_types[ctype] = true + var is_new_type := fx_host.mark_new_resource_type(ctype) var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri, - "is_first_dig_of_battle": not oc._facts.feat_first_dig_used, - "terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array), + "is_first_dig_of_battle": not facts.feat_first_dig_used, + "terrain_kind": minesweeper.harmful_variants(evt.get("variant_ids", []) as Array), "is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0 or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1, "is_blank_zero": board_b.digit_of(evt.cell) == 0, @@ -96,7 +119,7 @@ func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void: continue pos_m += int(h.get("mult", 0)) neg_m += int(h.get("cost_mult", 0)) - reward_grant.grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri}) + reward_grant.grant_routed(reward_grant.feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri}) _reveal_safe_apply(dr_hits, evt, ri) @@ -107,15 +130,15 @@ func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void: for h in dr_hits: if String(h.get("kind", "")) == "counter_add": _apply_counter_hit(h) - if not oc._hits_of(dr_hits, "reveal_tiles").is_empty(): + if not DamageController.hits_of(dr_hits, "reveal_tiles").is_empty(): fx_host.enqueue_reveal() for h in dr_hits: if String(h.get("kind", "")) == "grant_material": _apply_material_hit(h, evt.cell, ri) - var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell") - if not spell_hits.is_empty() and not oc._facts.feat_first_dig_used: - oc._facts.feat_first_dig_used = true - var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢", + var spell_hits: Array[Variant] = DamageController.hits_of(dr_hits, "spawn_spell") + if not spell_hits.is_empty() and not facts.feat_first_dig_used: + facts.feat_first_dig_used = true + var spell_t := damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢", {"channel": "board", "base_value": int((spell_hits[0] as Dictionary).get("value", 0)), "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": evt.cell, "attack_type": "magic"}) reward_grant.submit(spell_t, false) @@ -128,19 +151,19 @@ func _reveal_mine(evt: Dictionary, snap: Dictionary, ri: int) -> void: var mine_power := int(snap.get("power", 0)) var mh_hits := fx_host.fx_emit("mine_hit", {"cell": evt.cell, "round": ri, "damage": mine_power, "dug_under_seal": bool(evt.get("dug_under_seal", false)), - "hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))}) + "hp_ratio": int(round(100.0 * float(int(units.hero_unit().get("hp", 0))) / float(maxi(1, int(units.hero_unit().get("max_hp", 1))))))}) var applied := _reveal_mine_apply(mh_hits, snap, evt.cell, ri) if int(applied["mine_reduce"]) > 0: snap["power"] = maxi(0, mine_power - int(applied["mine_reduce"])) # 减伤落快照(负向兑付按减后值——安全帽每层减 1) for h in mh_hits: if String(h.get("kind", "")) == "counter_add": _apply_counter_hit(h) - if not oc._hits_of(mh_hits, "reveal_tiles").is_empty(): + if not DamageController.hits_of(mh_hits, "reveal_tiles").is_empty(): fx_host.enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付) - reward_grant.grant_routed(oc._feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": int(applied["halve_pct"])}) + reward_grant.grant_routed(reward_grant.feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": int(applied["halve_pct"])}) for h in mh_hits: if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect": - var nct := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", + var nct := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", {"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE, "ri": ri, "origin_cell": evt.cell}) reward_grant.submit(nct, false) @@ -162,15 +185,14 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in else: mine_reduce += int(h.get("value", 0)) # 背水——定值减免 "heal": - var hu_heal: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit() + var hu_heal: Dictionary[Variant, Variant] = units.hero_unit() hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1)))) "bonus_delta": var axis := String(h.get("axis", "")) - if fx_host._fx_bonus_delta.has(axis): - fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0)) + fx_host.add_bonus_delta(axis, int(h.get("delta", 0))) "blast_scaled": - var blast_v := maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1)))) - var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", + var blast_v := maxi(int(db.combat.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1)))) + var bs_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", {"channel": "board", "base_value": blast_v, "target_rule": DamageController.TARGET_ALL_ALIVE, "ri": ri, "origin_cell": cell}) reward_grant.submit(bs_t, false) @@ -182,21 +204,21 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in # 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径, # 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场) func _terrain_batch(ri: int) -> void: - for evt in oc._facts.reveals: + for evt in facts.reveals: var vids: Array[Variant] = evt.get("variant_ids", []) for vid in vids: - var vrow: Dictionary[Variant, Variant] = oc.db.combat.variation.row_by_id(String(vid)) + var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(String(vid)) if String(vrow.get("effect", "")) != "flat_damage": continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面) # 地形变异伤害单据——无类型必中,下限保底在控制器管线内 - # damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken: + # damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 emit_hero_damage_taken: # 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕; # hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射; # grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化)) var dt_amount := int(vrow.get("effect_value", 0)) - var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri) + var dt_res := damage.emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri) dt_amount = int(dt_res["total"]) - var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), + var vt := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), {"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO, "ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}}) reward_grant.submit(vt, false) @@ -210,7 +232,7 @@ func _terrain_batch(ri: int) -> void: # 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。 # 伤害单据案C 暂存战斗段末兑付。 func _seal_batch(ri: int) -> void: - for evt in oc._facts.seals: + for evt in facts.seals: _judge_seal(evt, ri) _seal_strike_batch(ri) @@ -220,7 +242,7 @@ func _seal_batch(ri: int) -> void: func _seal_strike_batch(ri: int) -> void: var fs_hits := 0 var fs_misses := 0 - for fevt in oc._facts.seals: + for fevt in facts.seals: if bool(fevt.get("hit", false)): fs_hits += 1 else: @@ -230,10 +252,10 @@ func _seal_strike_batch(ri: int) -> void: var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses) if strike.is_empty(): return - for fevt in oc._facts.seals: + for fevt in facts.seals: if not bool(fevt.get("hit", false)): continue - var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规", + var st_t := damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规", {"channel": "board", "base_value": int(strike["value"]), "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": fevt.cell, "attack_type": "normal"}) reward_grant.submit(st_t, false) @@ -272,16 +294,16 @@ func _round_end_apply(ri: int) -> void: for h in re_fx: if String(h.get("kind", "")) == "grant_gp": reward_grant.escrow += int(h.get("value", 0)) - oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}) + emit_row_cb.call({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}) elif String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": - oc.action_economy.queue_ap_delta(int(h.get("value", 0))) + action_economy.queue_ap_delta(int(h.get("value", 0))) # 转存率入参(储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;未装配=100 与 1:1 逐位一致) - var rap_pct: int = oc.attune_param_sum("rap_transfer_mult") - oc.action_economy.finish_round_stop(rap_pct if rap_pct > 0 else 100) - if oc._facts.path == "cleared": - reward_grant.grant_clear_reward(ri, oc._facts.board_from_midround, oc.perfect_sweep_earned()) + var rap_pct: int = int(attune_param_sum_cb.call("rap_transfer_mult")) + action_economy.finish_round_stop(rap_pct if rap_pct > 0 else 100) + if facts.path == "cleared": + reward_grant.grant_clear_reward(ri, facts.board_from_midround, bool(perfect_sweep_cb.call())) fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入) - oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) + damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) # round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点, @@ -294,12 +316,12 @@ func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void: var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", ""))) if layers <= 0: continue - var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit() + var hu: Dictionary[Variant, Variant] = units.hero_unit() if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)): - oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))}) + emit_row_cb.call({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))}) continue - var healed: int = oc._enemy_system.heal(hu, layers) - oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"}) + var healed: int = units.heal(hu, layers) + emit_row_cb.call({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"}) # round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名, @@ -310,10 +332,10 @@ func _round_end_blast_tickets(re_fx: Array, ri: int) -> void: var mg_layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("per", ""))) var mg_total := int(h.get("value", 0)) * mg_layers if mg_total > 0: - var mg_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter", + var mg_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter", "积层反噬 ×%d" % mg_layers, {"channel": "board", "base_value": mg_total, "target_rule": DamageController.TARGET_ALL_ALIVE, - "ri": ri, "origin_cell": oc.minesweeper.hero_pos()}) + "ri": ri, "origin_cell": minesweeper.hero_pos()}) reward_grant.submit(mg_t, false) @@ -329,12 +351,12 @@ func _judge_seal(evt: Dictionary, ri: int) -> void: # 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付) func _judge_seal_hit(evt: Dictionary, ri: int) -> void: var cell: Vector2i = evt.cell - if oc._facts.seal_consumed.has(cell): + if facts.seal_consumed.has(cell): return # 去重跳过;零明细行 - oc._facts.seal_consumed[cell] = true - oc._facts.mark_perfect_hit() + facts.seal_consumed[cell] = true + facts.mark_perfect_hit() var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri, - "terrain_kind": oc._harmful_adjacent(cell)}) + "terrain_kind": minesweeper.harmful_adjacent(cell)}) _seal_burst_tickets(fh_hits, cell, ri) _seal_hit_effects(fh_hits, cell, ri) # 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格; @@ -342,7 +364,7 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void: for h in fh_hits: if String(h.get("kind", "")) == "grant_material": _apply_material_hit(h, cell, ri, "seal_hit") - var snap = oc._reward_economy.grant(cell) + var snap = reward_economy.grant(cell) if not (snap is Dictionary): return reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"}) @@ -351,10 +373,10 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void: # 标中殉爆票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌 # 吃 N 群伤,无类型走 blast 通道;封印爆裂〔专长〕同面消费) func _seal_burst_tickets(fh_hits: Array, cell: Vector2i, ri: int) -> void: - for h in oc._hits_of(fh_hits, "open_ticket"): + for h in DamageController.hits_of(fh_hits, "open_ticket"): if String(h.get("channel", "")) != "blast": continue - var fbt := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂", + var fbt := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂", {"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE, "ri": ri, "origin_cell": cell, "highlight": "highlight"}) reward_grant.submit(fbt, false) @@ -373,37 +395,36 @@ func _seal_hit_effects(fh_hits: Array, cell: Vector2i, ri: int) -> void: fx_host.apply_ac_bonus(h) "bonus_delta": var axis_f := String(h.get("axis", "")) - if fx_host._fx_bonus_delta.has(axis_f): - fx_host._fx_bonus_delta[axis_f] += int(h.get("delta", 0)) + fx_host.add_bonus_delta(axis_f, int(h.get("delta", 0))) "remove_mines": - var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell]) + var removed: Array[Variant] = minesweeper.remove_mines_in([cell]) if not removed.is_empty(): - oc._emit_row({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}) + emit_row_cb.call({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}) "preview_area": _seal_preview_area(cell, ri) # 示警烟火施加面(3×3 邻格资源类型预告——类型去重集入 overlay;信息呈现零回写) func _seal_preview_area(cell: Vector2i, ri: int) -> void: - var board_p: BoardData = oc.minesweeper.current_board() + var board_p: BoardData = minesweeper.current_board() var types: Array[Variant] = [] for n in board_p.neighbors8(cell): - var n_snap = oc._reward_economy.query_full_snapshot(n) + var n_snap = reward_economy.query_full_snapshot(n) if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))): types.append(String((n_snap as Dictionary).get("cell_type", ""))) if reward_grant.treasure_inventory != null and not types.is_empty(): reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) - oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) + emit_row_cb.call({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) # 封印未中(每格实例一次去重——归因行与负向兑付同键,防逐轮惩罚轰炸;seal_missed 发射序: # grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付) func _judge_seal_miss(evt: Dictionary, ri: int) -> void: var cell: Vector2i = evt.cell - oc._facts.mark_perfect_miss() - if oc._facts.seal_miss_seen.has(cell): + facts.mark_perfect_miss() + if facts.seal_miss_seen.has(cell): return # 每格实例一次去重 - oc._facts.seal_miss_seen[cell] = true + facts.seal_miss_seen[cell] = true # 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错 # 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在 # 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮 @@ -411,17 +432,17 @@ func _judge_seal_miss(evt: Dictionary, ri: int) -> void: # adventure 域计数器闸) for h in fx_host.fx_emit("seal_missed", {"cell": cell, "round": ri}): if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": - oc.action_economy.queue_ap_delta(int(h.get("value", 0))) + action_economy.queue_ap_delta(int(h.get("value", 0))) elif String(h.get("kind", "")) == "forgive_seal_miss": # 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账 # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) - oc._facts.mark_perfect_forgiven() + facts.mark_perfect_forgiven() _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) - oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0, - {"effect_id": "insurance_used", "forgiven": oc._facts.perfect_forgiven}) - var hl := "perfect_broken" if oc._facts.perfect_hit_false - oc._facts.perfect_forgiven == 1 else "" - oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {}) - var snap = oc._reward_economy.grant(cell) + emit_row_cb.call({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0, + {"effect_id": "insurance_used", "forgiven": facts.perfect_forgiven}) + var hl := "perfect_broken" if facts.perfect_hit_false - facts.perfect_forgiven == 1 else "" + emit_row_cb.call({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {}) + var snap = reward_economy.grant(cell) if not (snap is Dictionary): return reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri}) @@ -435,6 +456,6 @@ func _apply_counter_hit(h: Dictionary) -> void: # grant_material 兑付块单点(三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载) func _apply_material_hit(h: Dictionary, cell: Vector2i, ri: int, source_class: String = "safe_cell") -> void: - var g_snap = oc._reward_economy.grant(cell) + var g_snap = reward_economy.grant(cell) if g_snap is Dictionary: reward_grant.grant_routed(g_snap, "positive", {"source_class": source_class, "cell": cell, "ri": ri, "label_suffix": String(h.get("label", ""))}) diff --git a/game/core/settlement/round_cycle.gd b/game/core/settlement/round_cycle.gd index 370e6c77..3de84ac1 100644 --- a/game/core/settlement/round_cycle.gd +++ b/game/core/settlement/round_cycle.gd @@ -1,15 +1,26 @@ ## 轮循环驱动件:每轮结算周期的唯一驱动——结算重入门闸(settling)、五段段序执行 ## (段序骨架冻结于 SettlementTable.segment_order)、开轮摇号(共识点 B:技能选择前置到轮次开始, -## 摇定即本拍结算的选技)、踩雷计数账(转存时点=摇号轮末)。 +## 摇定即本拍结算的选技)、踩雷计数账(转存时点=摇号轮末)、换图刷新流程件持有与交接。 ## 单一变化原因:轮结算周期的推进结构(段序、重入闸、摇号时序)。 -## 耦合边界:oc=宿主引用(五段段类既定同款直调形态)——经 oc 取 facts/协作件/行发射/出口分发; -## 五段构造参数与持有形态不变(grant/combat 三引用、aura/decay/verdict 单引用);判定段零改动 -## (_exit_distribution 留宿主);随机只经 oc.rng 的 behavior_roll 封闭流(流名、开流时点、取号序逐字); -## 零信号连接、零 await;随编排器每战新建同灭。 +## 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束(六子对象+协作件 +## 引用+读数引用+七回调+两读投影),解包自用七键(facts/units/fx_host/db/action_economy/rng/ +## emit_row_cb)后原束分发五段(各段同束自取所需),段类对编排器零引用;变值标量 +## (battle_result/round_index)走调用参数现值交付(settle_if_due/roll_round_actions 签名扩展); +## 换图刷新流程件=本件持有:五段构造后复制束写入 grant_seg 段间引用再建 BattleRefreshFlow +## (授予段引用直接交换——不再穿编排器链式取段)。随机只经注入 rng 的 behavior_roll 封闭流 +## (流名、开流时点、取号序逐字);零信号连接、零 await;随编排器每战新建同灭。 +## 类变量封闭面(15≤15 贴顶禁再增):束解包 7(facts/units/fx_host/db/action_economy/rng/emit_row_cb) +## +mines_hits+五段+_settling+_refresh_flow。 class_name RoundCycle extends RefCounted -var oc: BattleOrchestrator # 宿主引用(段类同款直调——facts/协作件/行发射/出口分发取数单点) +var facts: BattleFacts # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数) +var units: UnitContainer # 单位容器(摇号/解锁/冷却消费面) +var fx_host: BattleFxHost # 效果宿主件(enemy_hp_threshold 发射面) +var db: ConfigDb +var action_economy: ActionEconomy +var rng: RngServiceCore +var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形) var mines_hits: Dictionary[Variant, Variant] = {"total": 0, "last": 0, "round": 0} # 踩雷计数(本场累计/上轮转存/当轮累计三读数合一账) var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点) var _aura_seg = null # AuraSegment(②增益/减益段) @@ -17,27 +28,40 @@ var _combat_seg = null # CombatSegment(③战斗段) var _decay_seg = null # DecaySegment(④衰减段) var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归宿主) var _settling := false +var _refresh_flow: BattleRefreshFlow # 换图刷新流程件(本件持有——编排器两公开方法单行转发经此) -func _init(host: BattleOrchestrator, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void: - oc = host - # 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕——五行自宿主 _init 逐字迁入) - _grant_seg = GrantSegment.new(oc, fx_host_ref, reward_grant_ref) - _aura_seg = AuraSegment.new(oc) - _combat_seg = CombatSegment.new(oc, fx_host_ref, reward_grant_ref) - _decay_seg = DecaySegment.new(oc) - _verdict_seg = VerdictSegment.new(oc) +func _init(bundle: Dictionary) -> void: + for k in ["facts", "units", "fx_host", "db", "action_economy", "rng", "emit_row_cb"]: + assert(bundle.has(k), "[轮循环] 引用束缺键: %s" % k) + facts = bundle.facts + units = bundle.units + fx_host = bundle.fx_host + db = bundle.db + action_economy = bundle.action_economy + rng = bundle.rng + emit_row_cb = bundle.emit_row_cb + # 结算五段接线(五段同束自取所需;信号连接纪律不涉段〔段零信号连接〕) + _grant_seg = GrantSegment.new(bundle) + _aura_seg = AuraSegment.new(bundle) + _combat_seg = CombatSegment.new(bundle) + _decay_seg = DecaySegment.new(bundle) + _verdict_seg = VerdictSegment.new(bundle) + # 换图刷新流程件(段间引用交付:复制束写入授予段引用——授予段直接交换图流程件, + # 编排器 _rounds._grant_seg 跨两层链式取段随之消灭) + var flow_bundle: Dictionary[Variant, Variant] = bundle.duplicate() + flow_bundle["grant_seg"] = _grant_seg + _refresh_flow = BattleRefreshFlow.new(flow_bundle) # 结算重入门闸+段序体(原宿主 _maybe_settle 三闸与 _settle 合体——闸序 settling→battle_result→phase -# 逐字;终局早退不回置 _settling,_battle_over 接管) -func settle_if_due() -> void: - if _settling or oc.battle_result != "": +# 逐字;终局早退不回置 _settling,_battle_over 接管。battle_result/round_index=调用参数现值交付) +func settle_if_due(battle_result: String, ri: int) -> void: + if _settling or battle_result != "": return - if oc.action_economy.phase != oc.action_economy.PHASE_WAITING: + if action_economy.phase != action_economy.PHASE_WAITING: return _settling = true - var ri := oc.round_index # 段序编排(五段——段体归 game/core/settlement/,宿主只做行发射/开单原语单点) _grant_seg.run(ri) # ① 授予段(案C 闸随段装载) _aura_seg.run(ri) # ② 增益/减益段(续存实例组合行) @@ -53,31 +77,36 @@ func is_settling() -> bool: return _settling +# 换图刷新流程件读出口(编排器 can_refresh/player_request_refresh 两公开方法单行转发经此) +func refresh_flow() -> BattleRefreshFlow: + return _refresh_flow + + # ---- 开轮摇号(共识点 B:技能选择前置到轮次开始,数值计算留结算段——原宿主函数逐字迁入)---- # 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能 # (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从统一流顺序取号摇定本拍动作。 # 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。 # 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。 -# 摇定后、兑现前死亡 → 本拍技能作废。 -func roll_round_actions() -> void: - var pity_m: int = int(oc.db.combat.skill.pity_threshold) +# 摇定后、兑现前死亡 → 本拍技能作废。round_index=调用参数现值交付。 +func roll_round_actions(ri: int) -> void: + var pity_m: int = int(db.combat.skill.pity_threshold) # facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts, # 容器内直读主角行) - var facts := { - "round_index": oc.round_index, - "last_round_end": "none" if oc.action_economy.last_round_end().is_empty() else String(oc.action_economy.last_round_end().get("path_tag", "none")), + var facts_pack := { + "round_index": ri, + "last_round_end": "none" if action_economy.last_round_end().is_empty() else String(action_economy.last_round_end().get("path_tag", "none")), "mines_hit_last": int(mines_hits["last"]), "mines_hit_total": int(mines_hits["total"]), } - for u in oc._enemy_system.query_alive("enemy"): + for u in units.query_alive("enemy"): # Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行 # 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性 # 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机 # 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏) - for sid in oc._enemy_system.check_trigger_unlocks(u, facts): - var srow_u: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(sid) - oc._emit_row({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"}, - oc.round_index, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))], + for sid in units.check_trigger_unlocks(u, facts_pack): + var srow_u: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(sid) + emit_row_cb.call({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"}, + ri, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))], 0, {"skill_id": sid}) # enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射, # 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值) @@ -89,18 +118,18 @@ func roll_round_actions() -> void: break # target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记, # 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value) - var eht_hits := oc._fx_host.fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v, - "is_boss": String(oc._facts.encounter.get("node_type", "")) == "boss"}) + var eht_hits := fx_host.fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v, + "is_boss": String(facts.encounter.get("node_type", "")) == "boss"}) for h in eht_hits: if String(h.get("kind", "")) == "target_attack_bonus": u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0)) - var entries: Array[Variant] = oc._enemy_system.rollable_skills(u, facts) - if oc._enemy_system.basic_streak(u) >= pity_m: - var specials: Array[Variant] = oc._enemy_system.special_skills(u, facts) + var entries: Array[Variant] = units.rollable_skills(u, facts_pack) + if units.basic_streak(u) >= pity_m: + var specials: Array[Variant] = units.special_skills(u, facts_pack) if not specials.is_empty(): entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合 - var stream: RandomNumberGenerator = oc.rng.open_stream("behavior_roll") - oc._enemy_system.set_pending_action(u, SkillLottery.draw(entries, stream)) - oc._enemy_system.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记) + var stream: RandomNumberGenerator = rng.open_stream("behavior_roll") + units.set_pending_action(u, SkillLottery.draw(entries, stream)) + units.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记) mines_hits["last"] = mines_hits["round"] # 轮次真正结束转存(轮内部分结算不更新) mines_hits["round"] = 0 diff --git a/game/core/settlement/verdict_segment.gd b/game/core/settlement/verdict_segment.gd index dba782c6..b5fc8375 100644 --- a/game/core/settlement/verdict_segment.gd +++ b/game/core/settlement/verdict_segment.gd @@ -3,31 +3,45 @@ extends RefCounted # ⑤ 判定段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 # 职责:防御性优先级 hero 先判(双死结构不可达的纵深防御)→ 全量状态快照(despawn 前 # 组装——终局含 result)→ 终局行+收束分派 / 下一轮出口分发。 +# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 5 键 +# (units/emit_row_cb/emit_snapshot_cb/battle_over_cb/exit_distribution_cb)解包;行发射/快照/ +# 终局收束经回调回宿主保留面(exit_distribution 轮推进保留现设计——七段结构冻结)—— +# 对编排器零引用。 # 返回值 = 裁定("victory"/"defeat"/"continue")——宿主 _settle 据此保持旧早退语义 #(终局不回置 _settling,_battle_over 接管);本段零算法改动(🔴 零行为变更契约)。 -var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主 +var units: UnitContainer # 单位容器(hero 存活/敌存活判定) +var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形) +var emit_snapshot_cb: Callable # 宿主 _emit_settlement_snapshot——结算快照发射(保留面) +var battle_over_cb: Callable # 宿主 _battle_over——终局收束(保留面) +var exit_distribution_cb: Callable # 宿主 _exit_distribution——下一轮出口分发(保留面) -func _init(host) -> void: - oc = host +func _init(bundle: Dictionary) -> void: + for k in ["units", "emit_row_cb", "emit_snapshot_cb", "battle_over_cb", "exit_distribution_cb"]: + assert(bundle.has(k), "[判定段] 引用束缺键: %s" % k) + units = bundle.units + emit_row_cb = bundle.emit_row_cb + emit_snapshot_cb = bundle.emit_snapshot_cb + battle_over_cb = bundle.battle_over_cb + exit_distribution_cb = bundle.exit_distribution_cb func run(ri: int) -> String: var verdict := "continue" - if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0: + if int(units.hero_unit().get("hp", 0)) <= 0: verdict = "defeat" - elif oc._enemy_system.query_alive("enemy").is_empty(): + elif units.query_alive("enemy").is_empty(): verdict = "victory" - oc._emit_settlement_snapshot(verdict) # 全量状态快照(despawn 前组装——终局含 result) + emit_snapshot_cb.call(verdict) # 全量状态快照(despawn 前组装——终局含 result) if verdict == "defeat": - oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗失败", 0, {}) - oc._battle_over("defeat") + emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗失败", 0, {}) + battle_over_cb.call("defeat") return verdict if verdict == "victory": - oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗胜利", 1, {}) - oc._battle_over("victory") + emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗胜利", 1, {}) + battle_over_cb.call("victory") return verdict - oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "下一轮次", 0, {}) - oc._exit_distribution() + emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "下一轮次", 0, {}) + exit_distribution_cb.call() return verdict diff --git a/game/core/slot_store.gd b/game/core/slot_store.gd index 3b7a8746..e410feb3 100644 --- a/game/core/slot_store.gd +++ b/game/core/slot_store.gd @@ -1,6 +1,6 @@ ## 存档槽位文件域件(D3 拆件——自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、 ## 摘要窥视(peek 族)的唯一所有者。槽位态(路径/激活号/模板)随件承载;数据重置与读写盘 -## 经注入的 save 内部协作家族(_reset_to_default/_load_parsed/_rename_overwrite)完成—— +## 经注入的 save 公开协作家族(reset_to_default/data_snapshot/restore_data/rename_overwrite 等)完成—— ## 本件零 _data 副本零缓存(load 整体换新不陈旧)。 class_name SlotStore extends RefCounted @@ -23,9 +23,9 @@ func _init(save_ref, path: String = "") -> void: ## 注入槽派生名时激活号同步记账,注入任意隔离路径时激活号 0(写盘仍合法——隔离面行为零变化)。 func attach_path(path: String) -> void: _path = path - _save._read_only = false + _save.set_read_only(false) _active_slot = _slot_index_of(path) - _save._reset_to_default() + _save.reset_to_default() _save.load_from_disk() @@ -35,8 +35,8 @@ func attach_slot_template(template: String) -> void: _slot_template = template _path = "" _active_slot = 0 - _save._read_only = false - _save._reset_to_default() + _save.set_read_only(false) + _save.reset_to_default() # 槽文件路径唯一派生口(模板单点——禁第二处拼名) @@ -54,6 +54,11 @@ func has_write_target() -> bool: return _path != "" +# 写盘目标路径读投影(has_write_target 同族——save_manager 迁移口/写盘路径单点消费) +func active_path() -> String: + return _path + + # 进档原语(sv5,契约 6/8):激活槽 N+读盘,复用 load_from_disk 迁移/兜底链按槽成立。 # 高版本拒读槽:激活不成立、恢复切换前三态(路径/激活号/禁写位/数据)——防误写他槽; # 损坏槽:兜底链接管(.corrupt.bak 改名+空态继续,激活成立)。 @@ -62,17 +67,17 @@ func activate_slot(slot: int) -> bool: return false var prev_path := _path var prev_slot := _active_slot - var prev_ro: bool = _save._read_only - var prev_data = _save._data + var prev_ro: bool = _save.read_only() + var prev_data = _save.data_snapshot() _path = slot_path(slot) _active_slot = slot - _save._read_only = false - _save._reset_to_default() + _save.set_read_only(false) + _save.reset_to_default() if not _save.load_from_disk(): _path = prev_path _active_slot = prev_slot - _save._read_only = prev_ro - _save._data = prev_data + _save.set_read_only(prev_ro) + _save.restore_data(prev_data) return false return true @@ -84,17 +89,17 @@ func create_slot(slot: int) -> bool: return false var prev_path := _path var prev_slot := _active_slot - var prev_ro: bool = _save._read_only - var prev_data = _save._data + var prev_ro: bool = _save.read_only() + var prev_data = _save.data_snapshot() _path = slot_path(slot) _active_slot = slot - _save._read_only = false - _save._reset_to_default() + _save.set_read_only(false) + _save.reset_to_default() if not _save.write_to_disk(): _path = prev_path _active_slot = prev_slot - _save._read_only = prev_ro - _save._data = prev_data + _save.set_read_only(prev_ro) + _save.restore_data(prev_data) return false return true @@ -116,7 +121,7 @@ func delete_slot(slot: int) -> bool: if _active_slot == slot: _path = "" _active_slot = 0 - _save._reset_to_default() + _save.reset_to_default() return true @@ -126,12 +131,12 @@ func delete_slot(slot: int) -> bool: func peek_slot_summary(slot: int) -> Dictionary: if slot < 1 or slot > _save.SLOT_COUNT: return {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} # 越界=非槽位,非空槽 - return _save._peek_summary_at(slot_path(slot)) + return _save.peek_summary_at(slot_path(slot)) # 窥视注入口(契约 7 隔离面同款——测试/探针经路径注入隔离,禁各自拼槽文件名) func peek_summary_at(path: String) -> Dictionary: - return _save._peek_summary_at(path) + return _save.peek_summary_at(path) # 注入路径的槽号反查(命中槽派生名返回槽号,否则 0——激活号记账用,写盘判定以 _path 非空为准) @@ -146,8 +151,8 @@ func _slot_index_of(path: String) -> int: # 旧档字节原样落槽 1,读取时由正式进档路径的兜底/拒读链接管,不静默丢数据); # dst 已存在不重迁(幂等判据——判别锚④:去判据则重复迁移覆盖槽 1);迁移成功后 src 改名 # bak 留痕(不删——处置字节留痕,防再次隐式读写)。任一步失败:源保留原地,下次启动重试。 -# tmp+rename 原子形态与 write_to_disk 同纪律(改名原语 _rename_overwrite 归 save IO 域单点)。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。 -func _migrate_legacy_save(src: String, dst: String, bak: String) -> void: +# tmp+rename 原子形态与 write_to_disk 同纪律(改名原语 rename_overwrite 归 save IO 域单点)。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。 +func migrate_legacy_save(src: String, dst: String, bak: String) -> void: if not FileAccess.file_exists(src): return # 执行前置:旧档不存在=无操作 if FileAccess.file_exists(dst): @@ -156,10 +161,10 @@ func _migrate_legacy_save(src: String, dst: String, bak: String) -> void: if not _copy_file(src, tmp): push_warning("[存档序列化] 旧单档迁移失败(源保留,下次启动重试):%s → %s" % [src, tmp]) return - if not _save._rename_overwrite(tmp, dst): + if not _save.rename_overwrite(tmp, dst): push_warning("[存档序列化] 旧单档迁移改名失败(tmp 保留待查,源保留重试):%s → %s" % [tmp, dst]) return - _save._rename_overwrite(src, bak) + _save.rename_overwrite(src, bak) push_warning("[存档序列化] 旧单档已迁入 %s,原件留痕于 %s" % [dst, bak]) diff --git a/game/data/schemas/board_gen_table.gd b/game/data/schemas/board_gen_table.gd index 7212dc2d..1bbe250f 100644 --- a/game/data/schemas/board_gen_table.gd +++ b/game/data/schemas/board_gen_table.gd @@ -113,7 +113,12 @@ func _validate_shave_params(row: Dictionary) -> Array[Dictionary]: return errs -var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移) +var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移) + + +# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕) +func bind_resolver(resolver: ConfigDbResolver) -> void: + _resolver = resolver ## 进度行解析委托(消费面 db.combat.board_gen.resolve_row——R1 ceil/clamp 语义归 ConfigDbResolver) diff --git a/game/data/schemas/difficulty_table.gd b/game/data/schemas/difficulty_table.gd index 6b4a4e7d..31965519 100644 --- a/game/data/schemas/difficulty_table.gd +++ b/game/data/schemas/difficulty_table.gd @@ -30,7 +30,12 @@ func validate() -> Array: return errs -var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移) +var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移) + + +# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕) +func bind_resolver(resolver: ConfigDbResolver) -> void: + _resolver = resolver ## 难度修饰解析委托(消费面 db.run.difficulty.resolve_modifiers——clamp 语义归 ConfigDbResolver) diff --git a/game/data/schemas/enemy_table.gd b/game/data/schemas/enemy_table.gd index bb13f710..f99d9ab4 100644 --- a/game/data/schemas/enemy_table.gd +++ b/game/data/schemas/enemy_table.gd @@ -209,7 +209,12 @@ static func band_of(band_keys: Array, D: int) -> int: # 禁双算法并存〔状态记录 312〕;L 维 ceil/clamp 与越顶 clamp 语义归 resolver)。 -var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移) +var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移) + + +# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕) +func bind_resolver(resolver: ConfigDbResolver) -> void: + _resolver = resolver ## 带寻址行集解析委托(消费面 db.combat.enemy.resolve_rowset——D 维 band_of 落带语义归 ConfigDbResolver) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 2e1e9eb6..53cf5d8e 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -208,6 +208,7 @@ func _suites() -> Array: {"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/effect_port_test.gd", "expect": 0}, # 效果端口件(效果端口与掷格分件批 V1–V3——端口单实例/业务类零引擎直连源码锚/事实面闭集两对照) {"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入) + {"path": "res://tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd", "expect": 0}, # 结算接线同种子逐项 golden 对拍主证〔封装5.1〕(架构审查 5.1 封装修复——段类束注入改造零行为变更基线) {"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走 02 关联档选跑,卡片退役后补登记由 02 全套件承接) @@ -225,7 +226,8 @@ func _suites() -> Array: {"path": "res://tests/unit/core/tree_requires_level_test.gd", "expect": 0}, # R1 requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态) {"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役) {"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴) - {"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:直接 new/编排器接线/真实一轮走段序) + {"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序) + {"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚) {"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10) {"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3) {"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8) diff --git a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd new file mode 100644 index 00000000..3c5d16db --- /dev/null +++ b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd @@ -0,0 +1,501 @@ +# tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd — 结算接线同种子逐项 golden 对拍宿主(架构审查 5.1 封装修复批 R1 主证) +# 断言面 = 同种子定死驱动序下 step_row 全量行流 + settlement_snapshot 全量载荷 + battle_finished +# 载荷 + 收束 presentation_snapshot 关键键(round/battle_result/hero.hp/enemies.size()), +# 与 GOLDEN 常量逐位相等〔封装5.1 V1〕——段类束注入改造的零行为变更主证: +# 改前基线在未改树上固化(GOLDEN 为空时走转储通道取数),改后全程保持绿。 +# 驱动序(卡面逐字定死):begin_battle(voucher_rap=20) → 轮1 choose_dig 移动/挖/封邻格/停 → +# player_request_refresh(换图刷新链入流)→ 轮2 一挖一停 → 轮3 径直停手。 +# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload, +# ConfigDB 显式实例化构造注入+自建 RngService(掷点隔离形态)。 +# 取数通道:GOLDEN 为空时打印捕获序列 JSON(两格缩进)后按专用码 3 退出——非判定码族(0 绿/1 红/2 空跑)。 +# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +const ConfigDBScript := preload("res://core/config_db.gd") +const AutoPlayer := preload("res://sim/auto_player.gd") + +const TEST_SEED := 4242 # golden 固化种子(卡面定死——改前改后同种子对拍前提) +const DUMP_EXIT := 3 # 空跑转储专用退出码(GOLDEN 未固化态取数用) + +# 改前基线(JSON 两格缩进单文件固化——与捕获序列 JSON.stringify 逐字相等即绿;空串=未固化→转储模式) +const GOLDEN := r""" +{ + "final": { + "battle_result": "", + "enemies_n": 2, + "hero_hp": 21, + "round": 4 + }, + "finished": [], + "rows": [ + { + "display_name": "迟滞(下轮行动点 -1)", + "effect_id": "ap_delta", + "origin_cell": [ + 1, + 0 + ], + "pool_value": -1, + "round_index": 1, + "source_class": "mine_hit", + "type": "effect", + "value": -1 + }, + { + "display_name": "行动点(下轮行动点 +1)", + "effect_id": "ap_delta", + "highlight": "seal_hit", + "origin_cell": [ + 1, + 1 + ], + "pool_value": 0, + "round_index": 1, + "source_class": "seal_hit", + "type": "effect", + "value": 1 + }, + { + "ac": 1, + "display_name": "主角攻击", + "killed": false, + "normal": 2, + "origin_cell": [ + -1, + -1 + ], + "roll": 12, + "round_index": 1, + "source_class": "hero_attack", + "target": "地精一伙", + "target_id": "goblin", + "tier": 100, + "type": "damage", + "value": 2 + }, + { + "ac": 10, + "attack_type": "normal", + "baseline": 1, + "crit": true, + "display_name": "地精一伙 普通攻击(暴击)", + "origin_cell": [ + -1, + -1 + ], + "roll": 20, + "round_index": 1, + "skill_id": "attack", + "source_class": "enemy_action", + "target": "hero", + "tier": 100, + "type": "damage", + "value": 2 + }, + { + "ac": 10, + "attack_type": "normal", + "baseline": 1, + "display_name": "地精一伙 普通攻击(被压制)", + "origin_cell": [ + -1, + -1 + ], + "roll": 2, + "round_index": 1, + "skill_id": "attack", + "source_class": "enemy_action", + "target": "hero", + "tier": 25, + "type": "damage", + "value": 1 + }, + { + "display_name": "状态衰减", + "origin_cell": [ + -1, + -1 + ], + "round_index": 1, + "source_class": "", + "type": "decay", + "value": 0 + }, + { + "display_name": "下一轮次", + "origin_cell": [ + -1, + -1 + ], + "round_index": 1, + "source_class": "", + "type": "verdict", + "value": 0 + }, + { + "display_name": "暴走", + "effect_id": "enrage", + "origin_cell": [ + 0, + 0 + ], + "pool_value": 25, + "round_index": 2, + "source_class": "mine_hit", + "type": "effect", + "value": 25 + }, + { + "ac": 1, + "display_name": "主角攻击", + "killed": true, + "normal": 2, + "origin_cell": [ + -1, + -1 + ], + "roll": 2, + "round_index": 2, + "source_class": "hero_attack", + "target": "地精一伙", + "target_id": "goblin", + "tier": 100, + "type": "damage", + "value": 2 + }, + { + "ac": 10, + "attack_type": "normal", + "baseline": 1, + "display_name": "地精一伙 普通攻击(被压制)", + "origin_cell": [ + -1, + -1 + ], + "roll": 1, + "round_index": 2, + "skill_id": "attack", + "source_class": "enemy_action", + "target": "hero", + "tier": 25, + "type": "damage", + "value": 1 + }, + { + "display_name": "状态衰减", + "origin_cell": [ + -1, + -1 + ], + "round_index": 2, + "source_class": "", + "type": "decay", + "value": 1 + }, + { + "display_name": "下一轮次", + "origin_cell": [ + -1, + -1 + ], + "round_index": 2, + "source_class": "", + "type": "verdict", + "value": 0 + }, + { + "ac": 1, + "display_name": "主角攻击", + "killed": false, + "normal": 2, + "origin_cell": [ + -1, + -1 + ], + "roll": 8, + "round_index": 3, + "source_class": "hero_attack", + "target": "地精一伙", + "target_id": "goblin", + "tier": 100, + "type": "damage", + "value": 2 + }, + { + "ac": 10, + "attack_type": "normal", + "baseline": 1, + "display_name": "地精一伙 普通攻击(被压制)", + "origin_cell": [ + -1, + -1 + ], + "roll": 6, + "round_index": 3, + "skill_id": "attack", + "source_class": "enemy_action", + "target": "hero", + "tier": 50, + "type": "damage", + "value": 1 + }, + { + "display_name": "状态衰减", + "origin_cell": [ + -1, + -1 + ], + "round_index": 3, + "source_class": "", + "type": "decay", + "value": 0 + }, + { + "display_name": "下一轮次", + "origin_cell": [ + -1, + -1 + ], + "round_index": 3, + "source_class": "", + "type": "verdict", + "value": 0 + } + ], + "snaps": [ + { + "board": { + "h": 10, + "revealed": 42, + "seals": 1, + "seq": 1, + "w": 10 + }, + "buffs": [], + "depth": 1, + "economy": { + "ap": 0, + "phase": "Waiting", + "rap": 26, + "refresh_used": 0 + }, + "enemies": [ + { + "armor": "light", + "armor_class": 1, + "atk": 1, + "hp": 2, + "id": "goblin", + "max_hp": 4, + "pending_skill": "" + }, + { + "armor": "light", + "armor_class": 1, + "atk": 1, + "hp": 4, + "id": "goblin", + "max_hp": 4, + "pending_skill": "" + } + ], + "hero": { + "armor_class": 10, + "atk": 2, + "hp": 23, + "max_hp": 26 + }, + "kind": "settlement", + "level": 1, + "round": 1, + "round_facts": { + "mines": 1, + "path": "stopped", + "safe": 0, + "seal_events": 1 + }, + "spawn_count": 2, + "unit_count": 2, + "verdict": "continue" + }, + { + "board": { + "h": 10, + "revealed": 43, + "seals": 0, + "seq": 2, + "w": 10 + }, + "buffs": [], + "depth": 1, + "economy": { + "ap": 0, + "phase": "Waiting", + "rap": 25, + "refresh_used": 1 + }, + "enemies": [ + { + "armor": "light", + "armor_class": 1, + "atk": 1, + "hp": 4, + "id": "goblin", + "max_hp": 4, + "pending_skill": "" + } + ], + "hero": { + "armor_class": 10, + "atk": 2, + "hp": 22, + "max_hp": 26 + }, + "kind": "settlement", + "level": 1, + "round": 2, + "round_facts": { + "mines": 1, + "path": "stopped", + "safe": 42, + "seal_events": 0 + }, + "spawn_count": 2, + "unit_count": 1, + "verdict": "continue" + }, + { + "board": { + "h": 10, + "revealed": 43, + "seals": 0, + "seq": 2, + "w": 10 + }, + "buffs": [], + "depth": 1, + "economy": { + "ap": 0, + "phase": "Waiting", + "rap": 33, + "refresh_used": 0 + }, + "enemies": [ + { + "armor": "light", + "armor_class": 1, + "atk": 1, + "hp": 2, + "id": "goblin", + "max_hp": 4, + "pending_skill": "" + } + ], + "hero": { + "armor_class": 10, + "atk": 2, + "hp": 21, + "max_hp": 26 + }, + "kind": "settlement", + "level": 1, + "round": 3, + "round_facts": { + "mines": 0, + "path": "stopped", + "safe": 0, + "seal_events": 0 + }, + "spawn_count": 2, + "unit_count": 1, + "verdict": "continue" + } + ] +} +""" + +var _db + + +func _init() -> void: + _db = ConfigDBScript.new() + var load_ok: bool = _db.load_tables("res://data/tables", true) + Kit.check("前置:表加载零 error", load_ok and _db.diagnostics_count("error") == 0) + var orch = load("res://core/battle_orchestrator.gd").new(_db, _make_rng()) + var rows: Array = [] + var snaps: Array = [] + var finished: Array = [] + orch.step_row.connect(func(row: Dictionary) -> void: rows.append(row)) + orch.settlement_snapshot.connect(func(s: Dictionary) -> void: snaps.append(s)) + orch.battle_finished.connect(func(p: Dictionary) -> void: finished.append(p)) + var accepted: bool = orch.begin_battle({"level_index": 1, "node_depth": 1, + "hero_hp": int(_db.combat.hero.max_hp), "voucher_rap": 20}) + Kit.check("前置:begin_battle 受理", accepted) + if accepted: + _drive(orch) + var final_snap: Dictionary = orch.presentation_snapshot() + var captured := {"rows": rows, "snaps": snaps, "finished": finished, + "final": {"round": int(final_snap.get("round", -1)), + "battle_result": String(final_snap.get("battle_result", "")), + "hero_hp": int((final_snap.get("hero", {}) as Dictionary).get("hp", -1)), + "enemies_n": (final_snap.get("enemies", []) as Array).size()}} + if GOLDEN == "": + print("[golden-dump] begin") + print(JSON.stringify(captured, " ")) + print("[golden-dump] end") + quit(DUMP_EXIT) + return + # GOLDEN 三引号字面量首尾各带一个换行(多行字符串语法载体)——对拍两侧 strip 首尾空白后逐位比 + var captured_json := JSON.stringify(captured, " ").strip_edges() + var golden_json := GOLDEN.strip_edges() + if captured_json != golden_json: + print("[captured-dump] begin") + print(JSON.stringify(captured, " ")) + print("[captured-dump] end") + Kit.check("封装5.1 V1 同种子逐项对拍:行流/快照/终局载荷/presentation 逐位同 golden", + captured_json == golden_json) + quit(Kit.finish("settlement_wiring_snapshot_test")) + + +func _make_rng(): + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(TEST_SEED) + return rng + + +# 驱动序(卡面定死):轮1 挖+封+停 → 刷新 → 轮2 一挖一停 → 轮3 停手 +func _drive(orch) -> void: + var ms = orch.minesweeper + # 轮 1:choose_dig 非空则 move→dig→封首个未揭示主角邻格→stop + var pick: Dictionary = AutoPlayer.choose_dig(ms) + if not pick.is_empty() and pick.get("dig_cell", null) != null: + ms.settle_batch([{"action": "move", "cell": pick["hero_cell"]}]) + ms.settle_batch([{"action": "dig", "cell": pick["dig_cell"]}]) + var c := _first_unrevealed_adjacent(orch) + if c.x >= 0: + ms.settle_batch([{"action": "seal_place", "cell": c}]) + ms.settle_batch([{"action": "stop"}]) + # 轮 1 结算后刷新(voucher_rap=20 保证 RAP 抵折后价——覆盖换图刷新链) + orch.player_request_refresh() + # 轮 2:一挖一停 + var pick2: Dictionary = AutoPlayer.choose_dig(ms) + if not pick2.is_empty() and pick2.get("dig_cell", null) != null: + ms.settle_batch([{"action": "move", "cell": pick2["hero_cell"]}]) + ms.settle_batch([{"action": "dig", "cell": pick2["dig_cell"]}]) + ms.settle_batch([{"action": "stop"}]) + # 轮 3:径直停手 + ms.settle_batch([{"action": "stop"}]) + + +# 首个未揭示且与主角 8 邻接的格(卡面定死封印目标选取;无候选=返回 (-1,-1) 跳过封印) +func _first_unrevealed_adjacent(orch) -> Vector2i: + var ms = orch.minesweeper + var hero: Vector2i = ms.hero_pos() + var board = ms.current_board() + for y in board.height: + for x in board.width: + var c := Vector2i(x, y) + if not ms.is_revealed(c) and board.neighbors8(hero).has(c): + return c + return Vector2i(-1, -1) diff --git a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd.uid b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd.uid new file mode 100644 index 00000000..22f3eaec --- /dev/null +++ b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd.uid @@ -0,0 +1 @@ +uid://uys0a2kxc8a6 diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index 6437559d..f0291e77 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -668,7 +668,7 @@ func _smoke_feats() -> void: apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) var pm_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) var pm_bonus := 0 - for h in orch._hits_of(pm_hits, "attack_bonus"): + for h in DamageController.hits_of(pm_hits, "attack_bonus"): pm_bonus = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕", pm_bonus == 2) fx.rebuild(["fuse_wire"], [], {}) @@ -688,7 +688,7 @@ func _smoke_feats() -> void: apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) var ti_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) var ti_val := 0 - for h in orch._hits_of(ti_hits, "attack_bonus"): + for h in DamageController.hits_of(ti_hits, "attack_bonus"): ti_val = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕", ti_val == 2) fx.rebuild(["chain_traps", "pain_memory"], [], {}) @@ -724,7 +724,7 @@ func _smoke_feats() -> void: EffectEngine.counter_add(orch.fx_host().counters_ctx, "stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账 Kit.check("弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕", _hits_kind(me_hits, "counter_add").size() == 1) var me_atk := 0 - for h in orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"): + for h in DamageController.hits_of(fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"): me_atk = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕", me_atk == 4) Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty()) diff --git a/game/tests/unit/core/encapsulation_sweep_test.gd b/game/tests/unit/core/encapsulation_sweep_test.gd new file mode 100644 index 00000000..56acd449 --- /dev/null +++ b/game/tests/unit/core/encapsulation_sweep_test.gd @@ -0,0 +1,110 @@ +# tests/unit/core/encapsulation_sweep_test.gd — 架构审查 5.1 封装修复·五处结构性零命中扫描宿主 +# 断言面 = 七段断言组(全部带「扫描面非空前置+正锚」——非空防扫描面为空静默绿,正锚证新面在用): +# V2 settlement 七文件:非空+class_name 正锚+\boc\b 全文零命中(段类对编排器引用归零,含头注) +# V3 battle_orchestrator:非空+搬家五方法定义零命中(宿主原方法删除禁留壳) +# V4 adventure_runner:非空+_bp_store._ 零命中+bp_read 正锚(公开面在用) +# V5 save_manager:非空+slots._ 零命中+active_path 正锚;slot_store:非空+_save._ 零命中+公开面正锚 +# V6 grant_segment:非空+两 fx 私键零命中+mark_new_resource_type 正锚 +# V7 config_db:非空+._resolver 直写零命中+bind_resolver 正锚 +# 手法沿 player_copy_sweep_test 源 token 扫描先例(不并件——扫描面归属与尾缀语义本批特有)。 +# 运行:godot --headless --path game --script res://tests/unit/core/encapsulation_sweep_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + _sweep_settlement_segments() + _sweep_orchestrator_methods() + _sweep_breakpoint_pair() + _sweep_save_pair() + _sweep_fx_host_accumulators() + _sweep_config_resolver() + quit(Kit.finish("encapsulation_sweep_test")) + + +# 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) +func _src(path: String) -> String: + var src := FileAccess.get_file_as_string(path) + Kit.check("扫描面非空前置:%s" % path, src.length() > 0) + return src + + +# 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位) +func _zero(pattern: String, src: String, label: String, path: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 零命中〔封装5.1〕" % [label, pattern], re.search(src) == null) + + +# 正锚在案断言(正则命中 ≥1——新公开面/新形态在用) +func _anchor(pattern: String, src: String, label: String, path: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 正锚在案〔封装5.1〕" % [label, pattern], re.search(src) != null) + + +# ① V2 settlement 七文件(束注入换代——对编排器引用归零) +func _sweep_settlement_segments() -> void: + var classes := { + "res://core/settlement/grant_segment.gd": "GrantSegment", + "res://core/settlement/combat_segment.gd": "CombatSegment", + "res://core/settlement/round_cycle.gd": "RoundCycle", + "res://core/settlement/verdict_segment.gd": "VerdictSegment", + "res://core/settlement/battle_refresh_flow.gd": "BattleRefreshFlow", + "res://core/settlement/aura_segment.gd": "AuraSegment", + "res://core/settlement/decay_segment.gd": "DecaySegment", + } + for path in classes: + var src := _src(path) + if src.length() == 0: + continue + _anchor("class_name %s" % classes[path], src, "class_name 正锚(%s)" % classes[path], path) + _zero("\\boc\\b", src, "对编排器标识符全归零(%s)" % classes[path], path) + + +# ② V3 编排器搬家五方法零残留 +func _sweep_orchestrator_methods() -> void: + var src := _src("res://core/battle_orchestrator.gd") + if src.length() == 0: + return + _zero("func _hero_atk\\(|func _feat_yield_adjust\\(|func _harmful_variants\\(|func _harmful_adjacent\\(|func _hits_of\\(", + src, "编排器搬家五方法定义零残留(宿主删除禁留壳)", "res://core/battle_orchestrator.gd") + + +# ③ V4 断点域(adventure_runner → BreakpointStore 公开面) +func _sweep_breakpoint_pair() -> void: + var src := _src("res://core/adventure_runner.gd") + if src.length() == 0: + return + _zero("_bp_store\\._", src, "宿主对断点件私取归零", "res://core/adventure_runner.gd") + _anchor("_bp_store\\.bp_read\\(", src, "断点公开读侧在用", "res://core/adventure_runner.gd") + + +# ④ V5 存档双向公开面(save_manager↔slot_store) +func _sweep_save_pair() -> void: + var src_m := _src("res://core/save_manager.gd") + if src_m.length() > 0: + _zero("slots\\._", src_m, "save_manager 对槽位件私取归零", "res://core/save_manager.gd") + _anchor("slots\\.active_path\\(\\)", src_m, "槽路径读投影在用", "res://core/save_manager.gd") + var src_s := _src("res://core/slot_store.gd") + if src_s.length() > 0: + _zero("_save\\._", src_s, "slot_store 反向私取归零", "res://core/slot_store.gd") + _anchor("_save\\.(set_read_only|read_only|reset_to_default|data_snapshot|restore_data|peek_summary_at|rename_overwrite)", + src_s, "存档公开承接面在用", "res://core/slot_store.gd") + + +# ⑤ V6 效果宿主累积面(grant → BattleFxHost 公开累积方法) +func _sweep_fx_host_accumulators() -> void: + var src := _src("res://core/settlement/grant_segment.gd") + if src.length() == 0: + return + _zero("_fx_new_resource_types|_fx_bonus_delta", src, "授予段对效果宿主私键直写归零", "res://core/settlement/grant_segment.gd") + _anchor("mark_new_resource_type\\(", src, "首见登记公开方法在用", "res://core/settlement/grant_segment.gd") + + +# ⑥ V7 配置挂载面(config_db → 三 schema bind_resolver) +func _sweep_config_resolver() -> void: + var src := _src("res://core/config_db.gd") + if src.length() == 0: + return + _zero("\\._resolver\\s*=", src, "解析协作件直写归零", "res://core/config_db.gd") + _anchor("bind_resolver\\(_resolver\\)", src, "挂载口在用", "res://core/config_db.gd") diff --git a/game/tests/unit/core/encapsulation_sweep_test.gd.uid b/game/tests/unit/core/encapsulation_sweep_test.gd.uid new file mode 100644 index 00000000..d3e5e0db --- /dev/null +++ b/game/tests/unit/core/encapsulation_sweep_test.gd.uid @@ -0,0 +1 @@ +uid://dq1huyomw37m2 diff --git a/game/tests/unit/core/settle_segments_test.gd b/game/tests/unit/core/settle_segments_test.gd index 1714b14e..4002bdd8 100644 --- a/game/tests/unit/core/settle_segments_test.gd +++ b/game/tests/unit/core/settle_segments_test.gd @@ -15,23 +15,33 @@ func _init() -> void: quit(1) return - # V4-1 段类直接 new()(构造收宿主引用——五类同构) + # V4-1 段类直接 new()(引用束注入——编排器 _init 同款 20 键束组装,逐段 new(bundle) 成功断言) var oc = load("res://core/battle_orchestrator.gd").new(db, load("res://core/rng_service.gd").new()) - Kit.check_quiet(load("res://core/settlement/grant_segment.gd").new(oc, oc.fx_host(), oc.reward_grant()) != null, "V4 GrantSegment 直接 new()〔迭代27 T2〕") - Kit.check_quiet(load("res://core/settlement/aura_segment.gd").new(oc) != null, "V4 AuraSegment 直接 new()〔迭代27 T2〕") - Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(oc, oc.fx_host(), oc.reward_grant()) != null, "V4 CombatSegment 直接 new()〔迭代27 T2〕") - Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(oc) != null, "V4 DecaySegment 直接 new()〔迭代27 T2〕") - Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(oc) != null, "V4 VerdictSegment 直接 new()〔迭代27 T2〕") + var seg_bundle: Dictionary[Variant, Variant] = { + "facts": oc._facts, "units": oc._enemy_system, "damage": oc._damage, + "status_effects": oc._status_effects, "reward_economy": oc._reward_economy, + "reward_grant": oc.reward_grant(), "fx_host": oc.fx_host(), "minesweeper": oc.minesweeper, + "db": oc.db, "action_economy": oc.action_economy, "rng": oc.rng, + "emit_row_cb": oc._emit_row, "fatal_cb": oc._fatal, "install_cb": oc._install_and_reset, + "battle_over_cb": oc._battle_over, "exit_distribution_cb": oc._exit_distribution, + "emit_snapshot_cb": oc._emit_settlement_snapshot, "emit_composition_cb": oc._emit_composition_effects, + "attune_param_sum_cb": oc.attune_param_sum, "perfect_sweep_cb": oc.perfect_sweep_earned, + } + Kit.check_quiet(load("res://core/settlement/grant_segment.gd").new(seg_bundle) != null, "V4 GrantSegment 束构造〔迭代27 T2〕") + Kit.check_quiet(load("res://core/settlement/aura_segment.gd").new(seg_bundle) != null, "V4 AuraSegment 束构造〔迭代27 T2〕") + Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(seg_bundle) != null, "V4 CombatSegment 束构造〔迭代27 T2〕") + Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(seg_bundle) != null, "V4 DecaySegment 束构造〔迭代27 T2〕") + Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(seg_bundle) != null, "V4 VerdictSegment 束构造〔迭代27 T2〕") - # V4-2 编排器 _init 接线(段持宿主引用;段序编排面在位)——注册面批同代归约 V1: + # V4-2 编排器 _init 接线(段持注入引用同一性——宿主回指对象随束注入退役;段序编排面在位): # 段件宿主直持改经 _rounds(轮循环驱动件)承载,接线断言改指 _rounds 段族 - Kit.check_quiet(oc._rounds != null and oc._rounds.oc == oc, "V1 编排器接线·轮循环驱动件在座且宿主回指〔编排归约 V1〕") + Kit.check_quiet(oc._rounds != null and oc._rounds.facts == oc._facts, "V1 编排器接线·轮循环驱动件在座且束注入同一性〔编排归约 V1〕") Kit.check_quiet(oc._facts != null, "V1 编排器接线·战斗事实件在座〔编排归约 V1〕") - Kit.check_quiet(oc._rounds._grant_seg != null and oc._rounds._grant_seg.oc == oc, "V4 编排器接线·授予段(宿主回指)〔迭代27 T2〕") - Kit.check_quiet(oc._rounds._aura_seg != null and oc._rounds._aura_seg.oc == oc, "V4 编排器接线·增益/减益段〔迭代27 T2〕") - Kit.check_quiet(oc._rounds._combat_seg != null and oc._rounds._combat_seg.oc == oc, "V4 编排器接线·战斗段〔迭代27 T2〕") - Kit.check_quiet(oc._rounds._decay_seg != null and oc._rounds._decay_seg.oc == oc, "V4 编排器接线·衰减段〔迭代27 T2〕") - Kit.check_quiet(oc._rounds._verdict_seg != null and oc._rounds._verdict_seg.oc == oc, "V4 编排器接线·判定段〔迭代27 T2〕") + Kit.check_quiet(oc._rounds._grant_seg != null and oc._rounds._grant_seg.facts == oc._facts, "V4 编排器接线·授予段(束注入同一性·facts)〔迭代27 T2〕") + Kit.check_quiet(oc._rounds._aura_seg != null and oc._rounds._aura_seg.emit_composition_cb.get_object() == oc, "V4 编排器接线·增益/减益段(回调对象同一性)〔迭代27 T2〕") + Kit.check_quiet(oc._rounds._combat_seg != null and oc._rounds._combat_seg.fx_host == oc.fx_host(), "V4 编排器接线·战斗段(束注入同一性·fx_host)〔迭代27 T2〕") + Kit.check_quiet(oc._rounds._decay_seg != null and oc._rounds._decay_seg.status_effects == oc._status_effects, "V4 编排器接线·衰减段(束注入同一性·status_effects)〔迭代27 T2〕") + Kit.check_quiet(oc._rounds._verdict_seg != null and oc._rounds._verdict_seg.units == oc._enemy_system, "V4 编排器接线·判定段(束注入同一性·units)〔迭代27 T2〕") # counter_add 写口真实路径(geologist 订阅行承载:公共命令链 begin_battle→move→dig→stop # 经授予段 counter_add 单点落 counters_ctx battle 桶——受理载荷+公开读数口 fx_host().counters_ctx) var rng_c = load("res://core/rng_service.gd").new() From f9f2cb2d27a63f98d55a61dfd2407ea290b79014 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:08:32 +0800 Subject: [PATCH 04/19] =?UTF-8?q?iter51-=E5=8D=A13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/node_map_generator.gd | 3 +- game/tests/run_all.gd | 1 + .../unit/core/encapsulation_p2_sweep_test.gd | 76 +++++++++++++++++++ .../core/encapsulation_p2_sweep_test.gd.uid | 1 + 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 game/tests/unit/core/encapsulation_p2_sweep_test.gd create mode 100644 game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid diff --git a/game/core/node_map_generator.gd b/game/core/node_map_generator.gd index bcf3044e..56505126 100644 --- a/game/core/node_map_generator.gd +++ b/game/core/node_map_generator.gd @@ -7,7 +7,6 @@ class_name NodeMapGenerator extends RefCounted -const NODE_TYPES := ["battle", "boss", "reward", "rest", "shop"] const FILLER_TYPES := ["battle", "reward", "rest"] # 多终点图的支路互斥段必须各有独有类型(契约 5③),而商店与 Boss 是每条支路共有件, # 独有类型只能来自 battle/reward/rest 三件 ⇒ 归属大岔口各区域的填充节点取互斥签名类型。 @@ -133,7 +132,7 @@ func _vo_node_fields(nodes: Array, errs: Array, by_id: Dictionary, indeg: Dictio if by_id.has(id): errs.append({"category": "dup_key", "message": "节点句柄重复: %d" % id}) by_id[id] = n - if not NODE_TYPES.has(String(n["type"])): + if not LevelTable.NODE_TYPES.has(String(n["type"])): errs.append({"category": "enum", "message": "节点类型出域: %s" % n["type"]}) if int(n["depth"]) < 0: errs.append({"category": "domain", "message": "节点深度为负: %d" % n["depth"]}) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 53cf5d8e..92a10f55 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -228,6 +228,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴) {"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序) {"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚) + {"path": "res://tests/unit/core/encapsulation_p2_sweep_test.gd", "expect": 0}, # NODE_TYPES 词表单点化结构性扫描〔架构P2 V1/V2〕(架构审查 P2 代码面——生成器本地副本归零+schema 唯一定点,非空前置+正锚) {"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10) {"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3) {"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8) diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd b/game/tests/unit/core/encapsulation_p2_sweep_test.gd new file mode 100644 index 00000000..5bfa81e5 --- /dev/null +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd @@ -0,0 +1,76 @@ +# tests/unit/core/encapsulation_p2_sweep_test.gd — 架构审查 P2·NODE_TYPES 词表单点化结构性扫描宿主 +# 断言面 = 两组(均带「扫描面非空前置+正锚」): +# V1 node_map_generator:非空+本地 const NODE_TYPES 定义零命中+LevelTable.NODE_TYPES.has( 正锚 +# (词表唯一落点归 schema owner——表行校验 LevelTable;生成器 class_name 直引=S3 豁免面在案形态) +# V2 生产三目录(core/data/presentation)`NODE_TYPES :=` 定义恰好 1 处且落 data/schemas/level_table.gd +# 手法沿 encapsulation_sweep_test 源 token 扫描先例(两卡独立 worktree 独立批——不并件)。 +# 运行:godot --headless --path game --script res://tests/unit/core/encapsulation_p2_sweep_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + _sweep_generator() + _sweep_production_face() + quit(Kit.finish("encapsulation_p2_sweep_test")) + + +func _src(path: String) -> String: + var src := FileAccess.get_file_as_string(path) + Kit.check("扫描面非空前置:%s" % path, src.length() > 0) + return src + + +func _zero(pattern: String, src: String, label: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 零命中〔架构P2〕" % [label, pattern], re.search(src) == null) + + +func _anchor(pattern: String, src: String, label: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 正锚在案〔架构P2〕" % [label, pattern], re.search(src) != null) + + +# ① V1 生成器词表定义删除与改指 +func _sweep_generator() -> void: + var src := _src("res://core/node_map_generator.gd") + if src.length() == 0: + return + _zero("(?m)^const NODE_TYPES", src, "生成器本地词表副本归零") + _anchor("LevelTable\\.NODE_TYPES\\.has\\(", src, "schema 词表直引在用") + + +# ② V2 生产三目录定义唯一性(递归扫 .gd,恰好 1 处且落 level_table.gd) +func _sweep_production_face() -> void: + var files: Array[String] = [] + for dir_path in ["res://core", "res://data", "res://presentation"]: + _collect_gd(dir_path, files) + Kit.check("扫描面非空前置:生产三目录 .gd ≥ 1 件", files.size() > 0) + var re := RegEx.create_from_string("NODE_TYPES\\s*:?=") + var hits: Array[String] = [] + for f in files: + var src := FileAccess.get_file_as_string(f) + if src.length() > 0 and re.search(src) != null: + hits.append(f) + Kit.check("生产面 NODE_TYPES 定义恰好 1 处(实得 %d)〔架构P2〕" % hits.size(), hits.size() == 1) + if hits.size() == 1: + Kit.check("唯一定义落 data/schemas/level_table.gd〔架构P2〕", hits[0] == "res://data/schemas/level_table.gd") + + +# 递归收集 .gd(跳过 .godot 缓存与 .uid 边车) +func _collect_gd(dir_path: String, out: Array[String]) -> void: + var dir := DirAccess.open(dir_path) + if dir == null: + return + dir.list_dir_begin() + var name := dir.get_next() + while name != "": + var full := dir_path + "/" + name + if dir.current_is_dir(): + if not name.begins_with("."): + _collect_gd(full, out) + elif name.ends_with(".gd"): + out.append(full) + name = dir.get_next() + dir.list_dir_end() diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid b/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid new file mode 100644 index 00000000..dfa41c1c --- /dev/null +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid @@ -0,0 +1 @@ +uid://50nvccb1clcg From 7f512aa8b6f3761037fa0f972ba1c21126cbfe25 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:08:32 +0800 Subject: [PATCH 05/19] =?UTF-8?q?iter51-=E5=8D=A14=EF=BC=9A=E5=8D=A1?= =?UTF-8?q?=E2=91=A3(=E8=BF=AD=E4=BB=A351=C2=B7L1=E6=B7=B1=E5=BA=A63?= =?UTF-8?q?=E6=B7=B7=E7=BC=96)+=E6=89=B9=E6=94=B6=E5=8F=A3:=20(1,3)?= =?UTF-8?q?=E5=A4=A7=E5=9C=B0=E7=B2=BE=E8=A1=8C=E9=80=80=E5=BD=B9=E6=94=B9?= =?UTF-8?q?=E5=9C=B0=E7=B2=BE+=E7=86=8A=E5=9C=B0=E7=B2=BE=E6=B7=B7?= =?UTF-8?q?=E7=BC=96(=E8=A1=8C=E5=BA=8F=E7=86=8A=E5=9C=B0=E7=B2=BE?= =?UTF-8?q?=E5=9C=A8=E5=89=8D/=E8=A1=8C=E5=80=BC=E6=95=B4=E8=A1=8C?= =?UTF-8?q?=E5=A4=8D=E7=94=A8(1,2)/(1,0)/=E9=9D=99=E6=80=81=E9=AA=B0?= =?UTF-8?q?=E5=92=8C3)+=E8=A1=A8=E6=B3=A8=E5=9B=9B=E5=A4=84+=E4=B8=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E6=8D=A2=E9=94=9A=E4=B8=8E?= =?UTF-8?q?=E8=A1=A5=E6=96=AD=E8=A8=80(v1-v6/=E9=80=90=E8=A1=8C=E6=B1=82?= =?UTF-8?q?=E5=92=8C=E5=8F=A3=E5=BE=84/=E6=B1=A0=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E4=B8=A4=E9=94=AE/=E7=99=BD=E7=9B=92=E6=8A=ACGP)+=E5=85=A8?= =?UTF-8?q?=E4=BB=93hobgoblin=E9=9B=B6=E6=AE=8B=E7=95=99+=E4=BA=A7?= =?UTF-8?q?=E5=87=BA=E7=89=A9=E4=B8=A4=E6=8A=A5=E5=91=8A+=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E9=AA=8C=E6=94=B6=E5=AE=A1=E8=AE=A1=E6=8A=A5=E5=91=8A?= =?UTF-8?q?(=E7=BB=88=E5=88=A4=E9=80=9A=E8=BF=87)+=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E7=BC=BA=E6=BC=8F=E8=AE=A2=E6=AD=A3(level=5Fshop:1484=20bp=5Fs?= =?UTF-8?q?tore=E7=A7=81=E5=8F=96=E8=BF=81=E7=A7=BB=E8=A1=A5=E8=90=BD)+mas?= =?UTF-8?q?ter=E5=B9=B6=E5=85=A5(=E5=B0=81=E5=8D=B0=E5=8D=B0=E8=AE=B0?= =?UTF-8?q?=E7=B4=A0=E6=9D=90=E6=89=B9=E9=9B=B6=E5=86=B2=E7=AA=81,?= =?UTF-8?q?=E5=86=92=E7=83=9F=E7=BB=BF)+S13=E5=9F=BA=E7=BA=BF=E6=AD=A3?= =?UTF-8?q?=E8=A7=84=E9=80=9A=E9=81=93--refresh=E8=AE=B0=E9=94=AE=E6=94=B6?= =?UTF-8?q?=E5=8F=A3;=20=E5=8F=97=E5=BD=B1=E5=93=8D=E4=B8=83=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=E5=85=A8=E7=BB=BF(table=5Fguard=2083/round=5Fflow=201?= =?UTF-8?q?36/level=5Fshop=20195/damage=5Fcontroller=2050=E7=AD=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/art/art-assets-checklist.md | 4 +- design/art/art-bible.md | 29 +-- design/art/art-status-checklist.md | 4 +- design/gdd/02-系统清单.md | 2 +- design/gdd/08-Alpha版本标准.md | 6 +- design/gdd/systems/战斗循环/战斗表现.md | 4 +- design/gdd/ux/battle.md | 2 +- game/assets/art/board/seal_mark.png | Bin 123 -> 223 bytes game/data/tables/enemy.tres | 43 ++-- game/tests/static_scan.sh | 3 +- .../damage_controller_test.gd | 5 +- .../round_flow_skill_pool_test.gd | 26 ++- game/tests/unit/core/adventure_feats_test.gd | 2 +- .../unit/core/config_db_resolver_test.gd | 8 +- game/tests/unit/core/feat_vouchers_test.gd | 2 +- .../unit/core/level_shop_economy_test.gd | 9 +- game/tests/unit/data_config/config_db_test.gd | 2 +- .../data_config/table_structure_guard_test.gd | 65 ++++-- .../unit/presentation/battle_narrator_test.gd | 2 +- reports/iter51-反哺点-2026-11-04.md | 29 +++ reports/iter51-实现报告-2026-11-04.md | 102 ++++++++++ reports/iter51-验收审计-2026-11-04.md | 190 ++++++++++++++++++ tools/doc_term_audit.py | 2 +- tools/gdstyle_baseline.txt | 1 + 体验验证清单.md | 2 + 待办清单.md | 8 +- 26 files changed, 468 insertions(+), 84 deletions(-) create mode 100644 reports/iter51-反哺点-2026-11-04.md create mode 100644 reports/iter51-实现报告-2026-11-04.md create mode 100644 reports/iter51-验收审计-2026-11-04.md diff --git a/design/art/art-assets-checklist.md b/design/art/art-assets-checklist.md index 8a1d1026..1924aad5 100644 --- a/design/art/art-assets-checklist.md +++ b/design/art/art-assets-checklist.md @@ -117,6 +117,8 @@ ### 3.3 雷 / 封印印记 / 数字格面像素化 ⚙ +- **封印印记已换代**:形 = 环+十字法阵、纯黑 32×32,形与定值见 art-bible §3.2 与 §8.2「封印印记源档」;本节余下面 = 雷格纹样与数字格面。 + - **要什么**:格子样张通过后按冻结参数出全套砖面资产(含图类型换色板组)。 - **来源**:⚙ 脚本管线自产——未开/已开/危险格/变异纹带是扫雷专属语义(角嵌纹样已不存在),素材站没有对的;砖面**纹理底噪**可 ♻ 用 CC0 无缝石/土纹理作脚本管线的纹理输入,量化到色板(混合路线)。 - **前置**:§1.1 格子样张通过(**未跑**)。 @@ -162,7 +164,7 @@ | 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint;连续地面层在其下方,先保留 | ⚙/✦ 自产 | | 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | ✦/⚙ 自产(同 §2.3) | | 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | ♻ 粒子/碎块改色 + ⚙ 坍塌结构自产 | -| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记旗面走封印黑 `#000000`(现行封印色,色史归 art-bible §4.1);特效走运动+色相(现行取法——明度分层不动);现行占位 = 封印黑「旗」字形;印记常显形态可与 §3.3 旗面像素化合并出 | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | +| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记常显形态**已落位**——`seal_mark.png`(32×32 纯黑,环+十字法阵;形与定值见 art-bible §3.2/§8.2);施印瞬间特效**未实装**,仍缺(走运动+色相,明度分层不动) | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | | 弹道/退场等动画密度 | 战斗表现输入清单,方案未定 | 样张锁动画方案后启动 | ♻ 通用弹道/光点/消散粒子改色为主 | **现状**:三关舞台背景层与连续地面层已交付(2026-10-31 入包 `game/assets/art/scene/`:dungeon v10c / underdark v10c / deep_underdark v10c,均 1280×252;共用地面 v12b,1280×100,按作者裁保留原亮度 L≈0.132);小石台件已在役。 diff --git a/design/art/art-bible.md b/design/art/art-bible.md index b1301483..0e1ba0e4 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -1,6 +1,6 @@ # 美术圣经(Art Bible)— Dungeon Sweeper -> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.37,历次修订明细见 git 提交史 +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.40,历次修订明细见 git 提交史 > > **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。 > 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。 @@ -22,7 +22,7 @@ **原则一|预告上格**(取自 Into the Breach;锚定 P1 推理的掌控感、P2 规划的延迟兑现) -继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格内绘制优先级固定:**数字(含色牌) > 小人标记 > 旗 > 变异纹带 > 装饰变体 > 底色**,任何元素禁止遮挡数字。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡数字〉 +继承其格内信息分层与"攻击预告画在目标格上"的范式:一切威胁、伤害与结算预告必须直接绘制在雷图格本体上,禁止只进侧栏或悬浮提示;格内绘制优先级固定:**数字(含色牌) > 小人标记 > 封印印记 > 变异纹带 > 装饰变体 > 底色**,任何元素禁止遮挡数字。格面类型预读 = 类型微光单通道,色相+明度双码(色带见 §4.2、键契约见 §4.6;徽章小图标族保留给库存与结算明细;类型微光为底效泛光层,不入遮挡栈、不遮任何元素)。与 ITB 之别:只取其信息骨架,弃其冷暗军事色调。验收:目检〈预告画在雷图格本体上,任何元素不遮挡数字〉 *范围注:本原则约束"已存在的信息往哪里画",不新增信息种类;预告类信息以战斗侧 GDD 实际定义的为准(如 P2 已锁的结算来源标注),GDD 未定义的预告不因本条而存在。* @@ -38,7 +38,7 @@ 格子即地牢地砖:未开 = 覆尘完整砖,已开 = 碎裂裸露,雷 = 被惊动的巢穴(格态定义与玩家说法见《排雷玩法》),图类型(board_type)= 同款地砖换色板。图类型与变异格的视觉差异一律先做色板变体,不做新资产;一切危险状态必须"形状 + 颜色"双通道冗余,色弱可辨。像 Moonlighter 之处在砖缝质感与暖色道具,但装饰禁止伪装成逻辑差异——玩家须一眼分清"好看"与"有雷"。验收:目检〈危险状态均为形状+颜色双通道,色弱模拟下可辨〉 -格子同时是**内容舞台**,格与格的美术表达按格内实态分层多样化(画法细则 §3.2)。已开格按当格结果整格表达——产资源格嵌对应家族物件纹样(地图战斗资源九类型,§4.6.3)、纯安全格呈现干净裸土、危险格巢穴更凶。未开格在覆尘完整砖之上叠加**装饰变体**(裂纹/碎石斑/物件级点缀等氛围层),用途是破除大面积同砖平铺的重复感。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**:形与信息元素(数字/小人/旗/纹样)形状语言隔离,灰度下不与任何逻辑元素同形。零信息纪律:随机分布、与雷/安/类型零相关、禁止构成可读线索、分布每板一次跨轮不复掷。焦点预算约束:装饰不构成完整凸形、不抢读(§3.4);数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4)。未开格的类型预读画为**类型微光单通道**(色相+明度双码;仍只表达类型,不泄危险格与安全格——呈现契约见 §4.6.3;概念定义、可见范围与生命周期见《HUD框架》「未开格类型微光」节)。验收:目检〈物件级装饰与逻辑元素形状隔离;装饰不构成完整凸形〉 +格子同时是**内容舞台**,格与格的美术表达按格内实态分层多样化(画法细则 §3.2)。已开格按当格结果整格表达——产资源格嵌对应家族物件纹样(地图战斗资源九类型,§4.6.3)、纯安全格呈现干净裸土、危险格巢穴更凶。未开格在覆尘完整砖之上叠加**装饰变体**(裂纹/碎石斑/物件级点缀等氛围层),用途是破除大面积同砖平铺的重复感。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**:形与信息元素(数字/小人/封印印记/纹样)形状语言隔离,灰度下不与任何逻辑元素同形。零信息纪律:随机分布、与雷/安/类型零相关、禁止构成可读线索、分布每板一次跨轮不复掷。焦点预算约束:装饰不构成完整凸形、不抢读(§3.4);数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4)。未开格的类型预读画为**类型微光单通道**(色相+明度双码;仍只表达类型,不泄危险格与安全格——呈现契约见 §4.6.3;概念定义、可见范围与生命周期见《HUD框架》「未开格类型微光」节)。验收:目检〈物件级装饰与逻辑元素形状隔离;装饰不构成完整凸形〉 *设计测试:当新图类型或新变异格需要新视觉时,先换色板加形状,再谈新资产;当格子多样化与"好看/有雷"之辨冲突时,砍装饰,不动逻辑通道。* @@ -46,12 +46,12 @@ > 立意:扫雷数字可读性与暗色氛围相冲突,由一次格子样张测试定风格,具体形态由样张测试定稿。**样张** = 一张按本圣经参数画成的格子风格试验图,是砖面形态的定稿闸——样张通过前,§3.2/§4.1 的一切参数只是候选,通过后冻结为制作标准。 -**样张内容清单**(一图齐备,缺项即无效):未开格(覆尘完整砖,含至少两种装饰变体)、已开格(碎裂裸露,含 0 格与 1–8 全档数字)、危险格(被惊动的巢穴)、旗、小人两态(独立占格 + 与数字同格——后者须验"边框高亮不覆盖文本")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(已开格底亮墨直画组 + 复杂格底色牌后备组——素底/装饰变体底/变异纹带底至少三种格底上各一组数字)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**类型微光呈现组**(至少三个类型的微光在纯砖格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉 +**样张内容清单**(一图齐备,缺项即无效):未开格(覆尘完整砖,含至少两种装饰变体)、已开格(碎裂裸露,含 0 格与 1–8 全档数字)、危险格(被惊动的巢穴)、封印印记、小人两态(独立占格 + 与数字同格——后者须验"边框高亮不覆盖文本")、图类型色板变体组(§4.3 四槽换色,至少两套)、数字呈现组(已开格底亮墨直画组 + 复杂格底色牌后备组——素底/装饰变体底/变异纹带底至少三种格底上各一组数字)、已开格结果表达组(纯安全格 + 至少两类型物件纹样)、**类型微光呈现组**(至少三个类型的微光在纯砖格上呈现——样张为静态定格测试图,非运行时行为承诺,实际可见时机见《HUD框架》「未开格类型微光」节;含两两相邻格微光并列;微光格内零角嵌/箭头/加号类附加图形)。验收:目检〈样张图含清单全部组项〉 **验收项按序判定,第一项不过 = 整张否决(数字可读性定风格,风格服从可读)**: 1. **第一验收项|数字可读性(硬指标,可测)**:1–8 全档数字对其所在**已开格底**直画 WCAG 对比 ≥4.5:1(参数域见 §4.1:已开格底走世界底层档、棋盘数字墨亮族);复杂格底(物件纹样/变异纹带/微光叠加致直画对比不足)启用**色牌后备**,牌墨组合 ≥4.5:1(色牌与牌上深墨族数字的定值见 §4.1),牌缘灰度可分;0.5× 缩放抽检数字仍可辨;纹带/裂纹/巢穴纹/物件纹样禁止侵入数字字形占位区(砖心留白,§3.2)。验收:机检〈1–8 数字对已开格底直画与牌墨组合对比均 ≥4.5:1〉 -2. **格态三形可辨(灰度通道)**:未开/已开/雷三态转灰度截图后仍可逐格分类;明度差(墙亮/空间暗)与碎裂形同为合法分类通道,双通道冗余(§3.2 完整度叙事)。装饰变体禁止制造新的分类特征——同一格态下带与不带变体的灰度差异禁止成为三态分类线索。色弱模拟(去色相)下旗、巢穴纹不与裂纹混淆。**五组类型微光互辨**:色弱玩家的类型主通道 = 微光,色相+明度双码,微光以明度梯度承载分组(组内不细分,属 §2 信息面层内梯度);微光禁止侵入数字字形占位区,明度梯度禁止压破格底明度档(三态分类不受扰)。验收:目检〈三态灰度逐格可分类;五组微光互辨;色弱模拟下旗、巢穴纹不与裂纹混淆〉 +2. **格态三形可辨(灰度通道)**:未开/已开/雷三态转灰度截图后仍可逐格分类;明度差(墙亮/空间暗)与碎裂形同为合法分类通道,双通道冗余(§3.2 完整度叙事)。装饰变体禁止制造新的分类特征——同一格态下带与不带变体的灰度差异禁止成为三态分类线索。色弱模拟(去色相)下封印印记、巢穴纹不与裂纹混淆。**五组类型微光互辨**:色弱玩家的类型主通道 = 微光,色相+明度双码,微光以明度梯度承载分组(组内不细分,属 §2 信息面层内梯度);微光禁止侵入数字字形占位区,明度梯度禁止压破格底明度档(三态分类不受扰)。验收:目检〈三态灰度逐格可分类;五组微光互辨;色弱模拟下封印印记、巢穴纹不与裂纹混淆〉 3. **焦点预算成立(§3.4)**:任意一格至多一个完整凸形;数字所在格无第二焦点。验收:目检〈任一格至多一个完整凸形,数字所在格无第二焦点〉 4. **图类型换皮抽测(§4.3)**:变体组中数字墨亮族三档、封印黑、危险红、奖励金、小人蓝、数字色牌(后备件)、类型微光五组恒定不动;变体格底仍过第 1、2 项指标。验收:机检〈换皮后不变槽色值恒定,变体格底仍过第 1、2 项〉 @@ -59,7 +59,7 @@ **整屏样张闸**:格子样张验"格",整屏样张验"屏"——战斗屏/关卡屏/菜单屏/营地屏各出一张整屏样张,验收四项:①全部文字(含菜单/说明/tooltip/按钮)对其直接背景 ≥4.5:1(WCAG AA),大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;②明度分层三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4:首焦唯一——数字区/立绘/主按钮,无第二焦点竞争;负空间服务焦点,不属空窗);④文字禁直接压纹理底(垫容器或罩层)。验收顺序 = **方向稿目检先行**(防"机检过闸但气质否决"式返工),目检过后机检冻结。执行时机 = 屏级视觉落码开工前;过闸前一切屏级视觉为占位临时态(§7.1 完成条款)。**样张判定物的产出手段与素材输入级别不进圣经定值**——圣经只定样张要验什么,不定样张以何种手段产出、也不定它取哪一级别素材输入。验收:机检〈整屏样张四项断言全绿,且目检先行过审〉 -**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸待方向稿目检先行(真人检测,登记于体验验证清单 ㊶) +**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸(方向稿目检已通过,凭据=体验验证清单 ㊶) **样张现状**:格子样张已冻结,冻结凭据 = git 提交 7a847c7(机检报告)与 §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 亮墨三档直画 + 色牌后备(值见 §4.1);新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉 @@ -164,7 +164,7 @@ - **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三 - **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3 - **未开格预读通道(微光单通道)**:类型预读 = 类型微光单通道(色相+明度双码,色带 §4.2);色弱类型主通道 = 微光明度梯度(组粒度——组间互辨;§2 微光明度例外);格内零角嵌/箭头/加号类附加形(微光格只留微光)。验收:目检〈微光格内零角嵌/箭头/加号类附加形〉 ←原一;原三 -- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记;封印色见 §4.1(现行封印黑),格内优先级栈位见 §1.2。封印特效(施印瞬间 + 印记常显形态)资产归清单 §4 演出类;封印印记纹理已落位(现行件 = `seal_mark.png`,32×32 可选件),数字字形占位已不存在。验收:目检〈封印印记呈现与封印黑一致,不侵入数字字形占位区〉 ←原一;P2 +- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法纪律**:纯黑实心剪影,禁细线描边、禁符文字形——32px 下细线会断成碎点,阵纹一律做成实心块面。**尺寸**见 §8.2「封印印记源档」条(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),无发光、无描边、无渐变。**占位**:印记居格心,最外沿不越格缘空带(见 §8.2);封印印记只出现在未开格,不与数字同格。格内栈位见 §1.2。封印特效(施印瞬间 + 印记常显形态)资产归清单 §4 演出类——常显形态即本件,施印瞬间特效未实装。封印印记纹理已落位(现行件 = `seal_mark.png`)。验收:目检〈印记为环+十字、四端与环相接;无发光与描边;最外沿不越格缘空带〉 ←原一;P2 - **可挖域接触带微光(交互反馈层)**:挖掘期,英雄格周界与可挖域(概念见《排雷玩法》动作合法性节;呈现义务见《HUD框架》可挖域指示)的接触面画呼吸微光——正交邻墙 = 该侧整条边,斜向邻墙 = 共享角点 + 向两侧各延伸半条边;光只画英雄格(空间)一侧、不进墙格(与装饰变体零冲突,数字砖心不涉);微光 = 空间的灯光边界(画法规则)。色相 = 蓝族浅一档(§4.2 蓝条;与主角蓝框的分层由样张测试定,§1.4);呼吸 = §2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认 = 悬停金框(呈现交互归《HUD框架》悬停指示域)。验收:目检〈微光只画英雄格一侧接触面:正交邻墙整条边、斜向邻墙角点 + 半边〉 ←§2.1;§4.2;P1 - **墙体**:圆角砌块堆叠,禁尖刺装饰——尖刺专属危险语义,装饰禁止伪装逻辑差异。验收:目检〈墙体无尖刺装饰〉 ←原三 - **高墙瓦片体系|术语与定义**:未挖格 = **一整块 32×32 自含凸起岩体**;素材语义名——未开格素材 = **墙瓦片**,已开格素材 = **地面瓦片**。瓦片以自含岩体为单位,Tiny Dungeon 拼块套不进自含岩体定义,不作瓦片参考。验收:目检〈未开格为整块凸起岩体,无拼块接缝感〉 ←原三 @@ -173,7 +173,7 @@ - **高墙瓦片体系|凹角收头**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出。验收:目检〈凹角收头与转角竖线同列,无悬空突出〉 - **高墙瓦片体系|现行定稿**:`game/assets/art/board/terrain_*`(16 唯一墙瓦片 + 5 地面瓦片变体;词表与查表逻辑落码于 `board_world.gd`)。验收:机检〈terrain_* 素材 16 墙瓦片 + 5 地面变体在库,查表逻辑与词表一致〉 - **高墙瓦片体系|高墙感 D 投影档与整板悬崖**:南临空墙格 = 立面口沿高光 2px `#C9AF83`(201,175,131——代码色 `BRICK`,与掩尘枚同源单点) + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起);整板悬崖 = 板底/右侧 `DEPTH_PX 8px` 暗面 + 列分隔竖缝 + 岩层横纹 + 口沿高光 + 接触 AO + 底线收边 + 悬浮影(游戏 `board_world.gd` 同款结构件,样张脚本与游戏渲染常量同源)。验收:目检〈南临空墙格呈口沿高光+双段暗带;北邻墙地面格呈三段渐隐投影;整板悬崖结构件齐备〉 -- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/旗);原三 +- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与封印印记;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/封印印记);原三 ### 3.3 UI 形状语法(方向指导,细则见 §7.1) @@ -181,7 +181,7 @@ ### 3.4 主角形状与对比预算 -显著度 = 完整度:**每格至多一个"完整凸形"**(数字/小人/旗,依 1.2 优先级让位;装饰变体贴砖缘分布、不构成第二完整凸形——§4.6;数字色牌随数字一体,不作独立焦点;类型微光为底效泛光,不作焦点、不抢完整凸形预算),底砖与装饰一律残缺、内凹、低对比而自然后退;危险用刺与缺(排斥形),收益用满与圆(吸引形),同表预算、此消彼长。焦点分配双机制:**完整度**(形状,本条上文)+ **明度带**(§2 明度分层——信息面层恒亮于世界层,焦点天然落信息面);明暗只做层间分离,层内禁明暗竞争(不靠明暗抢焦)。验收:目检〈任一格至多一个完整凸形;信息面层恒亮于世界层〉 ←原二;P1;P3 +显著度 = 完整度:**每格至多一个"完整凸形"**(数字/小人/封印印记,依 1.2 优先级让位;装饰变体贴砖缘分布、不构成第二完整凸形——§4.6;数字色牌随数字一体,不作独立焦点;类型微光为底效泛光,不作焦点、不抢完整凸形预算),底砖与装饰一律残缺、内凹、低对比而自然后退;危险用刺与缺(排斥形),收益用满与圆(吸引形),同表预算、此消彼长。焦点分配双机制:**完整度**(形状,本条上文)+ **明度带**(§2 明度分层——信息面层恒亮于世界层,焦点天然落信息面);明暗只做层间分离,层内禁明暗竞争(不靠明暗抢焦)。验收:目检〈任一格至多一个完整凸形;信息面层恒亮于世界层〉 ←原二;P1;P3 --- @@ -199,7 +199,7 @@ - **棋盘数字呈现 = 按板自适应三态**:暗纹理板(dungeon 系)= **亮族**三档 1–3 冷亮青白 #D8ECF0/4–5 暖白 #F0E2C8/6–8 金亮 #F8E3A8 直画(判据 = 族最弱成员对 p90 ≥4.5:1 且对均值 ≥2.5:1);双峰纹理板(underdark/deep_underdark 系)= **色牌垫底**(素牌 DIGIT_PLATE + 深墨 #33261B 12:1,纹理免疫——双峰纹理板上任何墨族直画都不可读,渲染帧取证;判据 = 亮深两族皆不过门,深族均值门 3.0);缺盘平色回退态(#B39A78 中明度底)默认深墨直画。选模式管线落码单点 `board_view._digit_ink_mode`(三态:亮直/深直/落牌),机检面 = `terrain_tile_sets_test` 三关呈现模式断言;目视验证面 = board_shot_probe 渲染帧取证(机检≠看得清)。验收:机检〈terrain_tile_sets_test 三关呈现模式断言全绿,board_shot_probe 渲染帧目视复核〉 - **垫牌与 UI 数据墨 = 深墨族 `#33261B` 系**(L≤0.027,§7.2)。验收:机检〈垫牌与 UI 数据墨取深墨族,L≤0.027〉 - **八色弃用**:八色必与红/金/蓝/紫赤语义冲突,故弃。验收:方向性 -- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,旗杆米白保可读。旗朱与封印紫已不用于棋盘封印旗,红族棋盘侧只剩危险红;人设背包旗 = 旗朱红矩形旗,与棋盘封印色脱钩(人设侧见 character-design 背包旗条)。验收:机检〈封印宣告色 = #000000,未开格墙顶对其对比 ≥4.4:1〉 ←原一;P2 +- **封印黑 `#000000`**|封印宣告色,无色相:玩家的宣告"此格封印"。封印现行 = 无色相黑——紫色与微光蓝族(魔法微光 256° 等)色相冲突,黑色无色相天然避开 300°–345° 禁入带与全部微光色带;未开格墙顶明度 ≈113 对纯黑对比 ≥4.4:1,纯黑实心剪影单靠明度差即成立,不依赖任何浅色细件。封印紫已被封印黑取代、不再用于棋盘(色史归 git 提交史);红族棋盘侧只剩危险红;主角随身背包件为朱红矩形件(人设侧见 character-design),与棋盘封印色脱钩。验收:机检〈封印宣告色 = #000000,未开格墙顶对其对比 ≥4.4:1〉 ←原一;P2 - **危险红 `#A8321F`**|危险色:雷 = 被惊动的巢穴,兑现档红,更深更沉。验收:机检〈危险语义色 = #A8321F〉 ←原三;P2 - **奖励金 `#F0A828`**|已兑现的收益:GP/宝箱/蜡封,全作唯一亮金。验收:机检〈收益语义色 = #F0A828〉 ←P2;§2.3/2.4 - **小人蓝 `#2E86DE`**|小人主色:棋盘唯一"己方",与光标准线同族。验收:机检〈己方语义色 = #2E86DE〉 ←P1;§2.1 @@ -215,7 +215,7 @@ - **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#A8AEB8`)→ 并入 `FOG_MEMORY_INK #C4C4C4`,**部件取值 = `#C4C4C4`**;② 主角提灯铜色(原拟 `#C08A3A`)→ 并入 `GOLD_DEEP #B07A14`,**部件取值 = `#B07A14`**;③ 食人魔酋长暖褐皮(原拟 `#B08050` / `#A97C4E`)→ 并入 `暖石 #B8A582`,**部件取值 = `#B8A582`**;④ 石君王灰皮(原拟 `#9A968C` / `#949089`,按**次级皮**口径——板甲视同体块主色、灰皮属次级皮故不受 L 0.22–0.45 约束)→ 并入 `STONE #786E62`,**部件取值 = `#786E62`**;⑤ 轻甲护甲图标木色 `#8A5A34`(深素底下 2.72:1 不过门;原拟新增木色亮档 `#C49A5E`)→ 并入 `WARM_FAINT #CDB88C`,**部件取值 = `#CDB88C`**(8.08:1)。**一色两岗代价已随裁定接受**:① 的 `#C4C4C4` 语义岗 = 迷雾记忆墨(§6.5)、② 的 `#B07A14` 属金族(§4.2 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉 - **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①地台顶缘受光线 `#C4AC84` 已并入 `#C9AF83`(掩尘枚 `BRICK`,与 §8.2 高墙口沿高光同枚)——源常量已删除(全仓零引用,退役断言测试串为预期例外),绘制点全部改指 `#C9AF83`;②地台描边 `#5A4C3A` 已并入 `#4C4038`(代码 `PLAZA_EDGE`,ui_kit 唯一落点)——源常量已删除,绘制点全部改指;③舞台底 `#3E342A`(代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)——已随 §6.1 舞台背景插画批处置完毕(2026-11-02 迭代 48):源常量已删除(常量表零键),背景资产接管该绘制面,程序化平色缺图回退改指 `SLOT_BACKDROP`(`#3E362C`)。程序化回退绘制与已落合并值的贴图保持一致。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉 -**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9 +**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),封印印记、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9 **色板增量纪律(数值状态 = 待定现用)**:全作色板硬上限 **48 色**(防色板无限膨胀的封口条款;**额度已用尽、余量 0**,后续余量以色板检查器实报为准);新色入板须同时过两道门:①§4.2 语义检查(不占语义色段/禁入带);②**与既有任一色的 RGB 三通道差禁止全部 <8/255**——即三通道里至少一通道 ≥8/255 即过门,「三通道差全部 <8」才算不过门(条文原写的「三通道差 ≥8/255」有「逐通道全过」与「禁止全灭」两种互斥读法,前者下现行色板 247 对不达标、与本节自认的「现行即执法」合规状态不能同真,故取后者;用户裁)。**额度已用尽:后续任何新增须先压缩或合并既有色**——按本节「额度不足时呈报作者」口径呈报,**压缩或合并哪几枚由作者裁,本节不代裁、不预指对象**。反例实证:#3A2E22 ↔ #362B20 三通道差均 <5,肉眼不可辨,同板共存只增噪。不过门即触发「换色合并」(把意图色并进最近既有色),**禁加色绕行**;加色义务同时登记于本表修订。**「待定现用」态释义**:数值**现行即执法**(色板检查器按此拦,§8.3)——非候选(不存在"等冻结"空窗)、非死规(后续需求引入时可变);调整走本节修订并登记,改值 = 修订不是违约。验收:机检〈色板总数 ≤48;新色入板过两道门〉 @@ -420,7 +420,7 @@ - **菱形灯光区(遮罩几何)**:灯光区 = 以角色所在格为中心、**曼哈顿距离 ≤ LIGHT_RADIUS** 的格集,区内照常渲染。LIGHT_RADIUS 走表值/常量参数化,**基准 8**——现行事实源「灯光区起步半径=微光环 8 格」的落码取值(半径数值沿微光「周围 8 格」的 8 起步,实测后可调)。**两圈分立注**:类型微光作用圈 = 英雄贴身 8 邻格(与可挖域同圈,现行事实源《HUD框架》「未开格类型微光」节),**不随灯光区扩**(权威文档配套裁决)——灯光区只裁可见性,微光锚仍是可挖域,两圈几何分立、半径参数互不读写。提灯插值位:FOG_LANTERN_BONUS = **+2**(flat 整数档,权威文档配套裁决;落码常量名 `board_view.gd FOG_LANTERN_BONUS`),**已接效果入库**(载体=秘宝·同调位 fog_lantern「魔法提灯」行,组合根每战装配链显式重算接线;呈现画法零改动——本节只管"怎么画",机制现行事实源仍归《HUD框架》)。边界随角色格位逐格重算,无半径插值动画。←现行事实源;§2 光照氛围允许域 - **区外未开格:全黑不渲染(不透明档,照抄现行事实源)**:现行事实源原文「区外**未开格=全黑不渲染**」——不透明纯黑(L=0),**非高遮蔽半透档**。理由注记(为何不用高遮蔽档):①三档信息阶梯干净——未开格不可见=零信息、记忆格=弱信息、活格=全信息,叠读不打架;②高遮蔽档下砖面纹理隐约残留,与「类型微光=未开格唯一信息通道」(§4.2/§4.6.3)冲突——纹理残影泄格面观感;③现行事实源验收线「区外未开格不可见」须二值态才能逐格断言。←现行事实源;§4.2 -- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地板纹理原样 + 灰罩压顶**——纹理直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。双峰板色牌格在记忆态**退牌归板**——牌面并入记忆态、数字以记忆墨直画。**旗不在记忆态面**:旗只插在未开格,区外未开格全黑——旗印记随格隐没、灯光区移回复显(纯呈现,旗数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2 +- **区外已开格:暗淡记忆态(灰显可读档)**:画法 = **地板纹理原样 + 灰罩压顶**——纹理直画后 `FOG_MEMORY_PLATE #3A3A3A` α0.66 灰罩盖顶,素材零改动(原「程序化素色记忆板平涂」已弃用);格内元素灰阶随罩隐现:**记忆墨 #C4C4C4(中性灰 L≈0.55)**。双峰板色牌格在记忆态**退牌归板**——牌面并入记忆态、数字以记忆墨直画。**封印印记不在记忆态面**:印记只落在未开格,区外未开格全黑——印记随格隐没、灯光区移回复显(纯呈现,封印数据零影响,现行事实源的显式推论)。←HUD框架 可访问性基线;§4.1;§3.2 - **记忆态可读性判据**:数字对合成底**归一后(真实渲染面)三关 ≥4.5:1**(WCAG 硬指标;合成底 L1 #4C4038/L2 #3F4147/L3 #3E4048;行值与素材随动,测点=ui_contrast_probe 迷雾段)。**归一注**:装载亮度归一是现行生产路径(`board_view._normalize_floor_luma`),原图均值≠渲染面,复算须落归一后底。可辨性自查(三通道,随样张/实机自检):①对全黑未开格——合成底 #3E3731(L≈0.036)对 L0,实机目检常光档可分(暗房档一并抽检);②对灯光区内活态已开格——主判据=数字墨灰度差(记忆墨 L0.55 对亮墨三档 L≥0.75 / 色牌 #F5EAD3 L≈0.83,灰度 ΔL≥0.20)+ 色相通道(记忆态近中性灰,对活态暖底暖墨);③格态三形在记忆态保持灰度可辨——形通道经纹理+灰罩形态不变,沿 §1.4 验收 2 同源判据。验收:机检〈ui_contrast_probe 迷雾段记忆墨对比 ≥4.5:1〉←§1.4 - **迷雾过渡边:硬边(逐格二值)**:灯光区边界 = **整格二值切换**(区内全渲染 / 区外全黑或记忆态),**不做渐变过渡带、不做半透明晕边**。理由:①边界是信息边界不是氛围晕——渐变中间态 =「看得见但看不清」,破数字可读硬指标与记忆态明度档;②现行事实源验收断言(边界格呈态、两态逐位一致)须格级二值可判;③与砖面整格表达(§3.2 高墙瓦片体系)、ITB 格内信息分层气质同轴。无动画,减动效无涉。←现行事实源;§1.2 原则一;§3.2 - **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉 @@ -513,7 +513,7 @@ ### 8.2 像素基准与画布 - **缩放纪律(硬规则,按资产两类分级;不做个案豁免)**: - - **甲类·文本与语义件**(数字、图标、旗、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 + - **甲类·文本与语义件**(数字、图标、封印印记、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 - **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉 - **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×、**护甲类型图标三枚 24px=1×**——源 24×24,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉 - **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉 @@ -522,6 +522,7 @@ - **画布基准(暂定值)**: - **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致); - **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销; + - **封印印记源档 = 32×32、显示 1×**(甲类整数倍纪律):形 = 外环+十字(形与画法见 §3.2)。定值——**外环外径 23px(半径 11.6px)、环壁厚 2.6px;十字两臂各宽 3.2px、四端止于环内沿半径 9px(与环相接);圆心=格心;最外沿距格边 ≥4px**(该 4px 空带归变异纹带,§3.2 变异格形状通道)。纯黑实心剪影,单图像素域色数 = 1(底色透明)。改形须本节与 §3.2 同批修订。验收:目检〈件为 32×32 单色;外沿距格边 ≥4px;十字四端与环相接〉 - **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉 - **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉 - **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉 @@ -536,7 +537,7 @@ ### 8.4 帧数规格(样张冻结) - **帧数规格(样张冻结)**:待机循环 4 帧;攻击/技能准备 6 帧单次;受击 2 帧;死亡 = 灰化+碎裂退场(§4.6.4 语义硬规则),序列帧归 ✦ 缺料清单;帧时长与结算节拍对齐(预告时序见 §4.6.4)。验收:机检〈序列帧数与档位一致〉 -- **方向集**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟口径已追认(棋盘小人朝左时旗面朝左,与立绘侧「旗面恒画右」并存;画稿维持一份 + 镜像,零新增资产)。验收:目检〈四向帧齐、侧向镜像无破绽〉 +- **方向集**:棋盘小人=**一份画稿+左右镜像**(现行资产=朝左帧两态 idle/walk,右向 = `flip_h`;下/上向无独立帧);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟口径已追认(棋盘小人朝左时旗面朝左,与立绘侧「旗面恒画右」并存;画稿维持一份 + 镜像,零新增资产)。验收:目检〈侧向镜像无破绽〉 ### 8.5 结构件完整性与参考核验 diff --git a/design/art/art-status-checklist.md b/design/art/art-status-checklist.md index ac1138eb..3f8ed430 100644 --- a/design/art/art-status-checklist.md +++ b/design/art/art-status-checklist.md @@ -27,7 +27,7 @@ |---|---|---| | 格面资源徽章 19 枚 | ◐ | [art-bible](art-bible.md) §4.6 | | 关卡节点图图标组 | ◐ | [art-assets-checklist](art-assets-checklist.md) §3.2(正式 UI 设计未成文) | -| 雷/封印印记/数字砖面全套 | ◐ | [art-bible](art-bible.md) §1.4/§3.2(前置:格子样张) | +| 雷/封印印记/数字砖面全套 | ◐ 封印印记已换代(环+十字法阵);雷与数字砖面仍待格子样张前置 | [art-bible](art-bible.md) §1.4/§3.2 | | 增益状态效果图标 9 枚 | ✅ 已接入(2026-09-24) | [art-bible](art-bible.md) §4.6.1(候选原稿:状态图标批 2026-09-23 远程出图,归 candidates 档——路径描述性引用,归档纪律见 art-bible §8.3) | | 营地场景图 | ◐ | [art-bible](art-bible.md) §6.2 | | 字体(全局 UI 像素字,Fusion Pixel 单套单字重) | ✅ 已定稿在役(2026-09-27 接入;2026-10-24 用户裁现状即终稿——不另接拉丁专件、不上第二字重) | [art-bible](art-bible.md) §7.4 | @@ -38,7 +38,7 @@ |---|---|---| | 受击反馈 / 死亡表现 / 顺位标记 | ◐ 程序化演出在役(受击闪白+打击火花、灰化+碎裂退场〔禁瞬消〕、顺位徽标 ①②③ 程序绘制;帧级素材未产,§8.4 帧数规格候选) | [战斗表现](../gdd/systems/战斗循环/战斗表现.md) | | 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 | -| 封印特效(动作=封印,两件:施印特效+印记常显) | ⬜ | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | +| 封印特效(动作=封印,两件:施印特效+印记常显) | ◐ 印记常显已落位(`seal_mark.png` 环+十字);施印瞬间特效缺 | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | | 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) | | B 区舞台背景三关变体+共用连续地面层(场景件) | ✅ 已交付(2026-10-31 入包 `game/assets/art/scene/`:三张背景 1280×252、共用地面 1280×100;地面按作者裁保留候选 v12b 原亮度 L≈0.132) | [art-bible](art-bible.md) §6.1 · [battle-stage-prompts](battle-stage-prompts.md) §2 | diff --git a/design/gdd/02-系统清单.md b/design/gdd/02-系统清单.md index 9b2fb5d0..d6dc14b5 100644 --- a/design/gdd/02-系统清单.md +++ b/design/gdd/02-系统清单.md @@ -15,7 +15,7 @@ | `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) | | `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 | | `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 | -| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.26**,2026-10-13) | +| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.27**,2026-10-13) | ## systems/ 五个循环 diff --git a/design/gdd/08-Alpha版本标准.md b/design/gdd/08-Alpha版本标准.md index 606f7bf1..2d60b9f4 100644 --- a/design/gdd/08-Alpha版本标准.md +++ b/design/gdd/08-Alpha版本标准.md @@ -2,7 +2,7 @@ > **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。 > **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。 -> **状态**:**已定稿(v1.26)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。 +> **状态**:**已定稿(v1.27)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。 > **成文**:2026-10-13。 ## 本文怎么用 @@ -145,7 +145,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战 |---|---|---| | 像素美术风格与色板 | [美术圣经](../art/art-bible.md) | 在役 | | 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 | -| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史,复核转正归战斗一屏样张) | +| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史;观感复核随战斗一屏样张过闸完成,凭据=体验验证清单 ㊶) | | 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.md) | 待做(开工前置=先做素材轴/UI 轴对照截图定性,再按定轴修复;旧卡 v1.4 存 reports/ 作素材、非已写卡状态,重启须重新立卡;出口=用户实机目检第二、三关不再判「看着费劲」) | | 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 在役(九件已按档落位+机检全绿——原稿分辨率入包禁降采样、显示按三档绝对高 96/144/188 缩放、现有形象即终稿;定值单点=art-bible §8.2;实机目检清晰度与同屏体量顺序登记体验验证清单 ㊻) | | 敌人立绘(全敌种) | [角色设计](../art/character-design.md) | 在役 | @@ -156,7 +156,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战 | 减动效开关 | [设置与本地化](systems/表现与支撑/设置与本地化.md) | 在役 | | 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) | | UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(2026-10-24 用户裁现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) | -| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 部分在役(规格半已闭合=目标规格清单已落 art-bible 五张规格表〔2026-10-30 合回〕,五项实装定值与样张机检随迭代 43 入库;未结半=出口未达——方向稿用户目检(体验验证清单㊶)先行,样张整体过闸随其后。主角+三 Boss 重产依赖照旧;站桩帧动画已改排 Beta,见「六 · Alpha 不含」) | +| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 在役(规格半=目标规格清单已落 art-bible 五张规格表〔2026-10-30 合回〕,五项实装定值与样张机检随迭代 43 入库;出口半=方向稿用户目检已通过,样张整体过闸,凭据=体验验证清单 ㊶。主角+三 Boss 重产依赖照旧;站桩帧动画已改排 Beta,见「六 · Alpha 不含」) | | UI 氛围层与九宫格切片观感修 | [待办清单](../../待办清单.md) | 部分在役(光柱硬边已修=连续 alpha 渐变;未结半=九宫格切片错位——Alpha 内做最小修复:核对素材尺寸,必要时重出九宫格素材,全套素材重出排 Beta;出口=结算屏与图例屏错位消失,渲染闸+用户目检通过) | | UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:①滑轨填充随迭代 25 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) | diff --git a/design/gdd/systems/战斗循环/战斗表现.md b/design/gdd/systems/战斗循环/战斗表现.md index 8500de82..6165b5bd 100644 --- a/design/gdd/systems/战斗循环/战斗表现.md +++ b/design/gdd/systems/战斗循环/战斗表现.md @@ -1,7 +1,7 @@ # 战斗表现 > 一句话:战斗动画与演出(旧体系未单独成文) -> 状态:范围草案(待设计);现行为 v1.2 +> 状态:范围草案(待设计);现行为 v1.3 ## 设计目的 @@ -70,7 +70,7 @@ ### 前置美术测试是硬前置 -正式设计本文之前,必须先做一次"战斗表现美术测试":用一屏样张(一张按候选方案画成的试验图)锁定角色剪影、动画方案、战斗空间三件事。这是旧体系评审登记的排期硬前置,不是建议;battle.md 也登记了该测试须按双舞台布局出样。样张未过,不启动任何终稿资产与动画设计。 +正式设计本文之前,必须先做一次"战斗表现美术测试":用一屏样张(一张按候选方案画成的试验图)锁定角色剪影、动画方案、战斗空间三件事。这是旧体系评审登记的排期硬前置,不是建议;battle.md 也登记了该测试须按双舞台布局出样。样张未过,不启动任何终稿资产与动画设计。**该硬前置已解除**:一屏样张整体过闸(凭据=体验验证清单 ㊶)。 附着在同一次样张上的还有一项已登记的裁决(登记出处 = 敌人系统的呈现契约):敌人层绘制优先级的插入位置——白话就是敌人的姿态精灵画在绘制顺序的哪一层,会不会、以及如何与棋盘上的数字、封印印记、小人发生遮挡。它随美术测试一并裁决,不在本文提前定。 diff --git a/design/gdd/ux/battle.md b/design/gdd/ux/battle.md index ca1b89a2..2c38522d 100644 --- a/design/gdd/ux/battle.md +++ b/design/gdd/ux/battle.md @@ -125,7 +125,7 @@ > 布局为 B/C/D 三区——关卡进度条不在战斗屏(节点链进度常驻关卡画面,见 Navigation Position;字母不重编沿用 B/C/D)。 -> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;封印特效未实装,占位 = 封印黑「封」字形(棋盘视图程序化占位,现行代码仍为旧「旗」字形、随代码术语批换代,贴图在位=字形退役;旗朱→封印紫 2026-09-26→封印黑 2026-09-28 用户拍板)。 +> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;印记贴图已落位(`seal_mark.png`,环+十字法阵,形与定值见 art-bible §3.2/§8.2),缺图时回落封印黑「封」字形(棋盘视图程序化回退);封印特效的施印瞬间一件未实装。封印色史=朱红 → 封印紫 → 封印黑(现行)。 > 可挖域微光画法定稿(2026-09-25 用户拍板):**接触带·空间侧 + 蓝族**——光画英雄格周界接触面(正交邻墙=整边,斜向邻墙=角点+两半边),详见 art-bible §3.2;现行墙格整框绿相呼吸为待退役占位,实现切换归表现批(探针断言顺带)。 diff --git a/game/assets/art/board/seal_mark.png b/game/assets/art/board/seal_mark.png index ddf875e15783f8f27cb6e9e0fab2664f9ec9280c..225c28a09cec41d8a35b2962b3ae5654d3dff65b 100644 GIT binary patch delta 194 zcmV;z06qVE-vN*$f2K)9K~#90?Ur2*z#s^NZL|AdnOy(@fw?U6dz4V>A3Db2@1T$? z9a<dbItu9LRJl-U727JS>RYQJE0R6rVyPw)CuK w4=5cW&4gt{i!Zn0tzR;H>22、armor_class 3>>2—— ; 2026-09-24 阶名化解方案((1,3) 改名大地精以阶名归位化解同族血量倒挂观感)实机验证未达预期,按体验反馈重裁: ; 第一关总量曲线 12/10/50/35/72 的 5 倍墙消除(22 后曲线 12/10/22/35/72 相邻比最大 2.2);敌种 bugbear/池/数量/atk 区间零改动。 @@ -926,4 +945,4 @@ rows = [{ ; ; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss}——battle=战斗节点配池,boss=终点行集; ; depth_band=节点深度所在的带键,请求落带取首个 ≥ 深度的键(偏难一侧);越顶带由 resolver clamp、跨表校验判为调参事故。 -; hobgoblin 行为缺口行:无立绘、不在敌种表——留任在役,补齐三件套(立绘+语义模板+敌种表行)归敌种内容批。 +; (1,3) 大地精行 2026-11-04 用户裁退役,改地精+熊地精混编(行序熊地精在前、行值整行复用 (1,2)/(1,0)、静态骰和 3);沿革见 git 提交史 diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh index b8f9c873..f1c9adc0 100755 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -141,7 +141,7 @@ fi # 豁免单新增三处(2026-10-26 令牌配额口径清扫批 B4b 用户裁定保留):adventure_table.gd:26 档名映射对照注 # +adventure_table.gd:93 与 curio_deck.gd:130 四口径契约④原名引述——合法载体非残留,改写即断链 # 豁免单再增 13 处(2026-10-26 口径换代批用户裁「带日期历史读数注释保留原文」): -# hero.tres:21/:36、enemy.tres:751/:891、action_economy.tres:15/:16/:26/:27、status_effects.tres:91、 +# hero.tres:21/:36、enemy.tres:751、action_economy.tres:15/:16/:26/:27、status_effects.tres:91、 # meta.tres:48/:56/:62(.tres 十二处历史读数/带日期批记录)+level_autoplay.gd:560(完美档 41% 历史实证)—— # 历史读数原文锚定当时实测口径,改写即失真;现行口径描述不豁免(已随批换代) @@ -219,7 +219,6 @@ else if(FILENAME~"hero.tres"&&FNR==21)next if(FILENAME~"hero.tres"&&FNR==36)next if(FILENAME~"enemy.tres"&&FNR==751)next - if(FILENAME~"enemy.tres"&&FNR==891)next if(FILENAME~"action_economy.tres"&&FNR==15)next if(FILENAME~"action_economy.tres"&&FNR==16)next if(FILENAME~"action_economy.tres"&&FNR==26)next diff --git a/game/tests/unit/battle_settlement/damage_controller_test.gd b/game/tests/unit/battle_settlement/damage_controller_test.gd index 6430d0ac..d2cebb7e 100644 --- a/game/tests/unit/battle_settlement/damage_controller_test.gd +++ b/game/tests/unit/battle_settlement/damage_controller_test.gd @@ -13,6 +13,7 @@ const RngServiceScript := preload("res://core/rng_service.gd") const UnitContainerScript := preload("res://core/unit_container.gd") const StatusContainerScript := preload("res://core/status_container.gd") const DamageControllerScript := preload("res://core/damage_controller.gd") +const BattleFactsScript := preload("res://core/battle_facts.gd") const TEST_SEED := 20260928 # 夹具种子(基建值非玩法数值——回放确定性契约的输入) @@ -52,7 +53,7 @@ func _harness(db, depth: int = 0): rng.set_master_seed(TEST_SEED) var units = UnitContainerScript.new(db) var effects = StatusContainerScript.new(db) - var ctrl = DamageControllerScript.new(db, rng, units, effects) + var ctrl = DamageControllerScript.new(db, rng, units, effects, BattleFactsScript.new()) # facts 构造注入(5.1 封装修复——hero_atk 派生读消费面;夹具空 encounter=表值缺省) ctrl.emit_row_cb = Callable(self, "_on_row") effects.notify_battle_start() var spawn_ok: bool = units.spawn({"level_index": 1, "node_depth": depth, "hero_hp": int(db.combat.hero.max_hp)}) @@ -410,7 +411,7 @@ func _test_pool_clamp(db) -> void: # ---- 个体池读数 / 个体 AC 合流(增益状态 v1.5——敌方状态个体化批)---- func _test_pool_consumption_per_unit(db) -> void: - var h = _harness(db, 3) # depth 3 = hobgoblin 行集(unit_count 2-3——个体对照面需 ≥2 敌) + var h = _harness(db, 3) # depth 3 = 混编行集(熊地精 1 + 地精 1–2,最少 2 只——个体对照面需 ≥2 敌) if not h.spawn_ok: return var enemies: Array = h.units.all_enemies() diff --git a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd index c89a7401..ad2d3ab9 100644 --- a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd +++ b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd @@ -309,12 +309,15 @@ func _smoke_encounter_roll() -> void: scan_bad += 1 continue var n: int = settlement_scan.presentation_snapshot().enemies.size() - var erow2: Dictionary = _db.combat.enemy.resolve_rowset(1, int(depth_v), "battle")[0] # 行集首行(扫描面 = 战斗节点) - var lo2: int = int(erow2.get("unit_count", 1)) - var hi2: int = int(erow2.get("unit_count_max", lo2)) + var erowset2: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, int(depth_v), "battle") # 扫描面 = 战斗节点 + var lo2 := 0 + var hi2 := 0 + for er in erowset2: # 逐行求和(同带多行各掷各的——与 unit_container spawn 守卫同口径,非首行单行口径) + lo2 += int(er.get("unit_count", 1)) + hi2 += int(er.get("unit_count_max", int(er.get("unit_count", 1)))) if n < 1 or n < lo2 or n > hi2: scan_bad += 1 - Kit.check("全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" % scan_total, scan_bad == 0) + Kit.check("全深度 × 12 种子 spawn 全域内(%d 场,∈[Σunit_count, Σunit_count_max] 逐行求和且 ≥1)——空遭遇不可达(Bug#1 回归;混编行集口径)" % scan_total, scan_bad == 0) # 节点类型 → 敌人表 row_kind(非 Boss 战斗位恒 battle——深度带批寻址第三维) @@ -569,7 +572,8 @@ func _smoke_skill_pool_data() -> void: "1_battle_0_goblin": {"attack": 9, "lunge": 1, "desperate_guard": 1}, "1_battle_1_goblin": {"attack": 9, "lunge": 1, "desperate_guard": 1}, "1_battle_2_bugbear": {"attack": 6, "charge": 2, "heavy": 2}, - "1_battle_3_hobgoblin": {"attack": 6, "charge": 2, "heavy": 2}, + "1_battle_3_bugbear": {"attack": 6, "charge": 2, "heavy": 2}, + "1_battle_3_goblin": {"attack": 9, "lunge": 1, "desperate_guard": 1}, "1_boss_4_ogre_chief": {"attack": 6, "heavy": 2, "harden": 2, "quake_smash": 2}, "2_battle_0_bugbear": {"attack": 6, "lunge": 1, "heavy": 2}, "2_battle_1_bugbear": {"attack": 6, "lunge": 1, "heavy": 2}, @@ -608,7 +612,7 @@ func _smoke_skill_pool_data() -> void: got[String(entry.skill_id)] = int(entry.weight) if got != expect_pools[rk]: pools_ok = false - Kit.check("二十八行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1/1〔触发条件批+孤注守势 w1〕 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1/2〔触发条件批+趁隙偷袭 w2〕 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2——半血解锁收编技能行 trigger 闩型——迭代14④ T5/触发条件批迁移〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok) + Kit.check("二十九行十种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1/1〔触发条件批+孤注守势 w1〕 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1/2〔触发条件批+趁隙偷袭 w2〕 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2——半血解锁收编技能行 trigger 闩型——迭代14④ T5/触发条件批迁移〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok) var basic_count_ok := true for row in _db.combat.enemy.rows: var n := 0 @@ -688,7 +692,7 @@ func _smoke_skill_pool_data() -> void: and String(_db.combat.settlement.core.effect_pool_map.get("fortify", "")) == "enemy_defense") -# ② 同种子回放逐位复现(D3 大地精,多实例同种) +# ② 同种子回放逐位复现(depth 3 = 混编行集〔熊地精 1 + 地精 1–2〕) # 单敌轨迹不独立复放——轨迹取数与其它流交错,无法脱离全序重建; # 同种子全战斗逐位复现由 ② 全对拍承载,单点定位归事件日志。 @@ -708,7 +712,7 @@ func _fresh_rng(seed_val: int): # ---- ① GP 冒险钱包·到账 ---- -# ② 同种子回放逐位复现(D3 大地精,多实例同种) +# ② 同种子回放逐位复现(depth 3 = 混编行集〔熊地精 1 + 地精 1–2〕) # 单敌轨迹不独立复放——轨迹取数与其它流交错,无法脱离全序重建; # 同种子全战斗逐位复现由 ② 全对拍承载,单点定位归事件日志。 func _smoke_skill_pool_replay_independence() -> void: @@ -718,8 +722,8 @@ func _smoke_skill_pool_replay_independence() -> void: if not seqs.is_empty() and int(seqs.count) >= 2: seed_val = s break - Kit.check("多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" % seed_val, seed_val > 0) - # 敌 hp 低(表值 6)时 hero_atk=1 十轮内必然击杀(实例错位、轨迹断裂)—— + Kit.check("多实例种子可达(seed=%d——depth 3 混编遭遇 ≥2 实例)" % seed_val, seed_val > 0) + # 敌 hp 低(混编带地精 hp 8)时 hero_atk=1 十轮内必然击杀(实例错位、轨迹断裂)—— # 观测窗取 5 轮(5 拍 ×1 伤 < 6 HP 全员存活;仍覆盖保底 M=4 的触发窗)。 var rounds := 5 var run1 := _record_pool_trajectories(seed_val, rounds) @@ -1105,7 +1109,7 @@ func _smoke_enemy_buff_targets_self() -> void: var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg) var rows: Array = [] settlement.step_row.connect(func(row): rows.append(row)) - # L1 depth 3(hobgoblin 行集 2-3 只——depth 0 单 goblin 常被主角先手一击秒杀,死亡即不动手、cast 不发生) + # L1 depth 3(混编行集 熊地精 1 + 地精 1–2——depth 0 单 goblin 常被主角先手一击秒杀,死亡即不动手、cast 不发生) if not settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": int(_db.combat.hero.max_hp)}): continue for turn in range(4): diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index f0291e77..8d78c4c6 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -788,7 +788,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: autoplay._drive_level(a.runner(), st, null, "normal", "min") if String(st["result"]) != "": return false - if a._bp_store._pending_next_level: + if a._bp_store.pending_next_level(): # 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化) if not a.stop()._pending.is_empty(): var bcands: Array = a.stop()._pending["candidates"] diff --git a/game/tests/unit/core/config_db_resolver_test.gd b/game/tests/unit/core/config_db_resolver_test.gd index ce8e97ae..a28f2472 100644 --- a/game/tests/unit/core/config_db_resolver_test.gd +++ b/game/tests/unit/core/config_db_resolver_test.gd @@ -41,7 +41,7 @@ func _enemy_rows() -> Array: return [ {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin"}, {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "bugbear"}, - {"level_index": 1, "row_kind": "battle", "depth_band": 5, "enemy_id": "hobgoblin"}, + {"level_index": 1, "row_kind": "battle", "depth_band": 5, "enemy_id": "giant_spider"}, {"level_index": 1, "row_kind": "boss", "depth_band": 9, "enemy_id": "ogre_chief"}, ] @@ -56,10 +56,10 @@ func _difficulty_table(): func _test_band_hit() -> void: var hit: Array = _resolver.resolve_enemy_rowset(1, 4, "battle") Kit.check("带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕", - hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin") + hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "giant_spider") var exact: Array = _resolver.resolve_enemy_rowset(1, 5, "battle") Kit.check("精确落带:请求深度 5 命中带 5〔fv1〕", - exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin") + exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "giant_spider") func _test_rowset_order_preserved() -> void: @@ -75,7 +75,7 @@ func _test_top_band_clamp() -> void: _diags.clear() var rows: Array = _resolver.resolve_enemy_rowset(1, 99, "battle") Kit.check("越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕", - rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin") + rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "giant_spider") var clamp_hit := false for d in _diags: if d.contains("warning/resolve_depth_clamp"): diff --git a/game/tests/unit/core/feat_vouchers_test.gd b/game/tests/unit/core/feat_vouchers_test.gd index 04839ab6..4e4cc4ac 100644 --- a/game/tests/unit/core/feat_vouchers_test.gd +++ b/game/tests/unit/core/feat_vouchers_test.gd @@ -78,7 +78,7 @@ func _init() -> void: # 账本保留(契约 1):主动件充能与每冒险种行入口零改动(单源化后=差额种行幂等——不吞装配) a._attunement = ["incense_stick"] a._loadout.set_attunement(["incense_stick"]) - a._bp_store._loadout_seeded = false # 模拟首关 bind 时点(分派走 seed_for_adventure 分支——成员随断点件收编改址) + a._bp_store.set_loadout_seeded(false) # 模拟首关 bind 时点(分派走 seed_for_adventure 分支——成员随断点件收编改址) a._loadout_on_level_inventory() # 种行(首关 bind 时点语义) var incense_left := 0 for r in a.runner().treasure_inventory.snapshot_inventory(): diff --git a/game/tests/unit/core/level_shop_economy_test.gd b/game/tests/unit/core/level_shop_economy_test.gd index 2fcac170..5ddd10a9 100644 --- a/game/tests/unit/core/level_shop_economy_test.gd +++ b/game/tests/unit/core/level_shop_economy_test.gd @@ -724,7 +724,7 @@ func _smoke_level_graph() -> void: _db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1) _db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1) _db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1) - Kit.check("混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)", int(_db.combat.enemy.rows.size()) == 28) + Kit.check("混编断言段注入行全量还原(表回 29 行——L1 混编批后的基线行数;首批混编投放基线 21 行+覆盖 8 行)", int(_db.combat.enemy.rows.size()) == 29) # ⑬ 真图类型可见性(信息迷雾 v2.1):L2 入口停点纯核心面读数——候选 ⊆ 可见集 / # 终点恒可见 / 入口 BFS 层距 > N 的非豁免节点入雾。N 取数据表(代码零硬编码)。 @@ -1314,12 +1314,15 @@ func _smoke_shop_pool() -> void: expect_p = int(ceil(float(int(prow.get("base_price", 0))) * pow(_db.run.shop.price_b, float(l2 - 1)) * 1.0)) Kit.check("首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" % [ckind, int(c.get("price", -1)), expect_p], int(c.get("price", -1)) == expect_p) # ③ 买 1 全重抽:出账+加算区+账本三落点 + 现价 A 链递增(买后 visit 计数=1;TP0——消费在架令牌本体) + # 白盒抬 GP 保证首购场景确定构造(直改测试基建——非生产行为,A 链段同款先例;(1,3) 混编后战斗流 + # 消费序变化使 gp 分布漂移,种子扫描不再隐式保证首购空间——买链判据本身零变化) + r8.gp = int(r8.gp) + 128 var buy_c: Dictionary = {} for c in r8.shop.shop_shelf(): if int(c.get("price", 1 << 30)) <= int(r8.gp): buy_c = c break - Kit.check("可购卡在架(GP %d——种子扫描保证首购空间)" % int(r8.gp), not buy_c.is_empty()) + Kit.check("可购卡在架(GP %d——白盒抬 GP 保证首购空间)" % int(r8.gp), not buy_c.is_empty()) if not buy_c.is_empty(): var kind := String(buy_c.get("kind_id", "")) var tier := String(buy_c.get("tier", "")) @@ -1478,7 +1481,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool: autoplay._drive_level(a.runner(), st, null, "normal", "min") if String(st["result"]) != "": return false - if a._bp_store._pending_next_level: + if a._bp_store.pending_next_level(): # 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化) if not a.stop()._pending.is_empty(): var bcands: Array = a.stop()._pending["candidates"] diff --git a/game/tests/unit/data_config/config_db_test.gd b/game/tests/unit/data_config/config_db_test.gd index 47f77f21..d40a908a 100644 --- a/game/tests/unit/data_config/config_db_test.gd +++ b/game/tests/unit/data_config/config_db_test.gd @@ -219,7 +219,7 @@ func _test_rowset_discipline() -> void: Kit.check("Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)", errs.is_empty()) # 负向:同行集行不相邻 = rowset_rule 且报错定位到带键(R6 行序纪律 + R3 错误类别可定位) var t_gap = EnemyTableScript.new() - t_gap.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 9, "hobgoblin"), + t_gap.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "battle", 9, "giant_spider"), mk_row.call(1, "battle", 5, "bugbear"), mk_row.call(1, "battle", 0, "goblin"), mk_row.call(1, "boss", 9, "ogre_chief")]) errs = t_gap.validate() diff --git a/game/tests/unit/data_config/table_structure_guard_test.gd b/game/tests/unit/data_config/table_structure_guard_test.gd index f1d5bfeb..7f927de2 100644 --- a/game/tests/unit/data_config/table_structure_guard_test.gd +++ b/game/tests/unit/data_config/table_structure_guard_test.gd @@ -1,7 +1,7 @@ # tests/unit/data_config/table_structure_guard_test.gd — 表结构守卫单元测试宿主 # 断言面 = data/tables 各表的结构锚与守卫,七套: # adventure 冒险表(档位配额/件值对/同调目录 12 件/装配→战斗贯通) -# enemy 敌人表(20 行带键行序/hobgoblin 留任/三 Boss 行) +# enemy 敌人表(29 行带键行序/混编行集/三 Boss 行) # AC 单轨与掷点参数(status_effect/reward/settlement 三处无双轨/免伤墙护栏/敌 AC 带宽) # variation 地形变异(行数/候选池门控/跨字段互锁负样本) # difficulty 难度表(空表零行为/修饰套乘/越域 clamp) @@ -206,15 +206,15 @@ func _smoke_adventure_table() -> void: v18_ok and locked_n == 12) -# ---- 敌人表深度带键结构(迭代 34 建·迭代 35 覆盖批换代:28 行键序=覆盖表序·hobgoblin 留任·Boss 行集带键清单)---- +# ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:29 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单)---- func _smoke_enemy_bands() -> void: - print("--- 敌人表深度带键结构(28 行键序 · hobgoblin 留任 · Boss 带键覆盖表)---") - # ① 28 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约) + print("--- 敌人表深度带键结构(29 行键序 · (1,3) 混编 · Boss 带键覆盖表)---") + # ① 29 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前) # 迭代 35 生成图深度扩面覆盖批随动:战斗带顶扩到各关最大生成深度(L1→6/L2→9/L3→13)、 # Boss 行按终点深度分带(L1 6/L2 8·9/L3 11·12·13),模板时代带键 4/9/9 全部在册不动 var expect_keys: Array[String] = [ - "1|battle|0|goblin", "1|battle|1|goblin", "1|battle|2|bugbear", "1|battle|3|hobgoblin", + "1|battle|0|goblin", "1|battle|1|goblin", "1|battle|2|bugbear", "1|battle|3|bugbear", "1|battle|3|goblin", "1|battle|6|bugbear", "1|boss|4|ogre_chief", "1|boss|6|ogre_chief", "2|battle|0|bugbear", "2|battle|1|bugbear", "2|battle|4|giant_spider", @@ -229,16 +229,45 @@ func _smoke_enemy_bands() -> void: for row in _db.combat.enemy.rows: got_keys.append("%d|%s|%d|%s" % [int(row.get("level_index", -1)), String(row.get("row_kind", "")), int(row.get("depth_band", -1)), String(row.get("enemy_id", ""))]) - Kit.check("敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕", - _db.combat.enemy.rows.size() == 28 and got_keys == expect_keys) - # ② hobgoblin 缺口行留任随迁(不删行不补立绘——补齐归敌种内容批) - var hob := {} + Kit.check("敌人表 29 行且键序 = 覆盖表序((1,3) 两行相邻、熊地精在前)〔L1混编 v1〕", + _db.combat.enemy.rows.size() == 29 and got_keys == expect_keys) + # ② (1,3) 混编构成与行序+静态最坏骰和+行值整行复用(R1-R4) + var rowset13: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, 3, "battle") + var ids13: Array[String] = [] + var dice_sum := 0 + for r in rowset13: + ids13.append(String(r.get("enemy_id", ""))) + dice_sum += int(r.get("unit_count_max", int(r.get("unit_count", 1)))) + var expect_ids13: Array[String] = ["bugbear", "goblin"] + Kit.check("(1,3) 行集恰两行、enemy_id 序列 == [bugbear, goblin](行序=文件序=生成序——默认目标取首行)〔L1混编 v2〕", + rowset13.size() == 2 and ids13 == expect_ids13) + Kit.check("(1,3) 静态最坏骰和 Σ(unit_count_max,固定行缺省) == 3〔L1混编 v3〕", dice_sum == 3) + var row12 := {} + var row10 := {} for row in _db.combat.enemy.rows: - if String(row.get("enemy_id", "")) == "hobgoblin": - hob = row - Kit.check("hobgoblin 行在 (1,battle,3) 留任随迁(缺口行不删不补——补齐归敌种内容批)〔fv12〕", - int(hob.get("level_index", -1)) == 1 and String(hob.get("row_kind", "")) == "battle" \ - and int(hob.get("depth_band", -1)) == 3) + if int(row.get("level_index", -1)) == 1 and String(row.get("row_kind", "")) == "battle" \ + and int(row.get("depth_band", -1)) == 2: + row12 = row + if int(row.get("level_index", -1)) == 1 and String(row.get("row_kind", "")) == "battle" \ + and int(row.get("depth_band", -1)) == 0: + row10 = row + var mix_ok := rowset13.size() == 2 + if mix_ok: + for fld in ["hp", "atk", "atk_min", "atk_max", "pool", "attack_type", "armor_type", "armor_class", "unit_count", "unit_count_max"]: + mix_ok = mix_ok and rowset13[0].get(fld) == row12.get(fld) and rowset13[1].get(fld) == row10.get(fld) + Kit.check("(1,3) 两行逐字段 == (1,2)/(1,0) 行(hp/atk 区间/pool/ac/类型轴——零数值新增,不写字面量)〔L1混编 v4〕", mix_ok) + # ②b 表内 enemy_id 集合不含 hobgoblin(退役键零残留) + var id_set := {} + for row in _db.combat.enemy.rows: + id_set[String(row.get("enemy_id", ""))] = true + Kit.check("表内 enemy_id 集合不含 hobgoblin〔L1混编 v5〕", not id_set.has("hobgoblin")) + # ②c 深度 4/5 落带不变(band 6 单行 bugbear——本批零触碰护栏) + var rowset14: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, 4, "battle") + var rowset15: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, 5, "battle") + var deep_ok: bool = rowset14.size() == 1 and rowset15.size() == 1 \ + and String(rowset14[0].get("enemy_id", "")) == "bugbear" and String(rowset15[0].get("enemy_id", "")) == "bugbear" \ + and int(rowset14[0].get("depth_band", -1)) == 6 and int(rowset15[0].get("depth_band", -1)) == 6 + Kit.check("(1,4)/(1,5) battle 行集 == 单行 bugbear(band 6)〔L1混编 v6〕", deep_ok) # ③ Boss 行集带键 = 覆盖表(旧判据「各恰一行=图 Boss 节点深度」属单终点模板时代; # 生成图多终点要求每终点深度解析到互异带键 ⇒ 每关 Boss 行必然多于一行—— # 迭代 35 覆盖批随动改写为带键清单,「带键互异」的运行时把关在 level_shop_economy_test〔v11〕) @@ -335,7 +364,8 @@ func _smoke_ac_swap_guard() -> void: # ③c 熊地精首遇带降档锚定 + 第一关总量曲线护栏 # 首遇带行 = 数据驱动定位(契约 8:该 (1,battle,bugbear) 的最低 depth_band)锚定值 hp 22 / ac 2; # 敌种 bugbear 与池 attack6/charge2/heavy2 钉死不动(防顺手改多); - # 曲线护栏 = 深度带升序相邻带「hp × 中位数量」总量比 ≤ 2.5(12/10/22/35/72 现行全绿;不用单调不回撤——会红在首带→次带) + # 曲线护栏 = 深度带升序相邻带「hp × 中位数量」逐带累加总量比 ≤ 2.5(12/10/22/34/72 现行全绿;不用单调不回撤——会红在首带→次带; + # 同带多行按行累加——(1,3) 混编两行同带,单行覆盖口径在双行行集下失真,与 R6 spawn 守卫逐行求和同口径非放宽) var l12_band := _first_encounter_band(1, "bugbear") var l12_row: Dictionary = {} for row in _db.combat.enemy.rows: @@ -356,7 +386,8 @@ func _smoke_ac_swap_guard() -> void: continue var cnt_lo: int = int(row.get("unit_count", 1)) var cnt_hi: int = int(row.get("unit_count_max", cnt_lo)) - l1_totals[int(row.get("depth_band", -1))] = int(row.get("hp", 0)) * (cnt_lo + cnt_hi) / 2.0 + var bkey: int = int(row.get("depth_band", -1)) + l1_totals[bkey] = float(l1_totals.get(bkey, 0.0)) + int(row.get("hp", 0)) * (cnt_lo + cnt_hi) / 2.0 var curve_ok := true var curve_detail := "" var depths_sorted := l1_totals.keys() @@ -368,7 +399,7 @@ func _smoke_ac_swap_guard() -> void: if ratio > 2.5: curve_ok = false curve_detail += " (%d→%d):%.2f" % [depths_sorted[i - 1], depths_sorted[i], ratio] - Kit.check("第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" % [ + Kit.check("第一关相邻深度带总量比 ≤ 2.5(12/10/22/34/72——迭代 24 N1 曲线护栏,带键升序逐带累加;越带%s)" % [ curve_detail if curve_detail != "" else "无"], curve_ok) # ④ 掷点参数域(加载校验已过——此处钉形状语义) var bounds: Array = _db.combat.settlement.damage_roll.roll_tier_bounds diff --git a/game/tests/unit/presentation/battle_narrator_test.gd b/game/tests/unit/presentation/battle_narrator_test.gd index 494ef192..09255c20 100644 --- a/game/tests/unit/presentation/battle_narrator_test.gd +++ b/game/tests/unit/presentation/battle_narrator_test.gd @@ -111,7 +111,7 @@ func _test_snapshot_effects_per_unit() -> void: var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20261103) var settlement = load("res://core/battle_orchestrator.gd").new(db, rng) - # depth 3 = hobgoblin 行集 2-3 只——两卡对照面 + # depth 3 = 混编行集(熊地精 1 + 地精 1–2,最少 2 只)——两卡对照面 var open_ok: bool = settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": int(db.combat.hero.max_hp)}) Kit.check("V8 前置:开局(L1 depth3 双敌行集)", open_ok) if not open_ok: diff --git a/reports/iter51-反哺点-2026-11-04.md b/reports/iter51-反哺点-2026-11-04.md new file mode 100644 index 00000000..a1da6912 --- /dev/null +++ b/reports/iter51-反哺点-2026-11-04.md @@ -0,0 +1,29 @@ +# 迭代 51 反哺点清单(2026-11-04) + +> 实现批禁写 `design/`——以下各条由用户裁决后归文档批落文。裁决一条落一条,落完把该条清掉。 + +## 一、design/engineering/architecture/architecture.md(十处) + +1. **:124(BattleFxHost 行)**:「A3 action_economy(queue_ap_delta/charge_layers 读)」依赖句删 charge_layers 读——蓄力层账本已退役(卡①),A3 对 action_economy 依赖余 queue_ap_delta 通道。 +2. **:146(轴戊 EffectPort 句)**:「闭面五键」改「闭面四键」;「facts 取值束 dig_streak/charge_layers/rider_tier_*」删 charge_layers——biz_fact 闭面收窄为 dig_streak/hp_ratio/rider_tier_bonus/rider_tier_crit(卡①,键退役后宿主应答 null)。 +3. **:125(RoundCycle 行)**:「三参直构(host/fx_host_ref/reward_grant_ref——五段构造透传不落成员)」→「引用束注入(编排器 20 键封闭束,解包自用七键后原束分发五段;持有 BattleRefreshFlow);公开方法 3→4(refresh_flow() 读出口);类变量 8→15(贴顶禁再增)」(卡②)。 +4. **:123(BattleRefreshFlow 行)**:「static 两入口收 oc」→「实例束注入 8 键(facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg);宿主 can_refresh/player_request_refresh 单行转发形态不变,经 RoundCycle.refresh_flow() 取件;grant_seg 段间引用直接交接(编排器 _rounds._grant_seg 跨两层链式取段消灭);类变量 0→8」(卡②)。 +5. **:127(settlement/ 五段段类行)**:构造形态句 →「引用束注入(各段断言各自必需键);段类对编排器零引用(\boc\b 零命中——跨件单向接线,注入 Callable 亦属接线面)」(卡②)。 +6. **:122(damage_controller 行)**:类变量 12→13(+facts 构造注入);公开面净增 hero_atk()/static hits_of()/emit_hero_damage_taken 公开化(_emit 去下划线非并存)(卡②)。 +7. **:121(RewardGrant 行)**:+feat_yield_adjust(_feat_yield_adjust 自编排器归位)(卡②)。 +8. **:120(BattleFxHost 行)**:+mark_new_resource_type/+add_bonus_delta 两公开累积方法(类变量 15 不变,方法-only)(卡②)。 +9. **核心层表 A2 minesweeper_rules 行**:公开面 19→21(贴顶超 1——**随卡② S13 停卡呈报待用户裁**:特批准记基线则登记 21 贴顶超阈值态并立还债条目;harmful_variants/harmful_adjacent 自编排器归位)(卡②)。 +10. **核心层表 breakpoint_store 行+save_manager/slot_store 行**:breakpoint_store 公开方法面 5→18(13 新面+bp_read 公开化);save_manager 公开面 7→14(+set_read_only/read_only/reset_to_default/data_snapshot/restore_data+peek_summary_at/rename_overwrite 两更名公开化);slot_store「双向公开面」(+active_path;migrate_legacy_save 公开化;反向私取 21 处清零)(卡②)。 +11. **:119(B1 battle_orchestrator 行)**:「NODE_TYPES 词表双份」表述删句、改「词表单点归 LevelTable(生成器 class_name 直引——S3 豁免面在案形态)」(卡③)。 + +## 二、GDD 确认行 + +- design/gdd/systems/战斗循环/战斗结算.md:**零反哺**(明细流水词表、五段段序、段边界零触碰——零行为变更批,golden 逐位对拍在案)。 +- design/gdd/systems/表现与支撑/数据配置.md:**零反哺**(热重载规格保留原状——P2 卡不收件面)。 +- 术语表:**零新增术语**(引用束注入/读投影/公开方法面均仓库现行词)。 +- 卡④:难度曲线与Boss.md/敌人系统.md/08-Alpha版本标准.md **零动作**(文档批已随写卡段落文,本批零新增反哺)。 + +## 三、待办清单(实现批不碰,归收尾段/用户) + +- 卡② S13 一键(minesweeper_rules 公开方法 21>20):待用户按实现报告 §六裁决;若特批准记基线,建议同批立「minesweeper_rules 公开面还债」条目归后续批。 +- EffectEngine.has_effect_on 零消费方(卡① 备案):是否立死码处置条目,归用户裁。 diff --git a/reports/iter51-实现报告-2026-11-04.md b/reports/iter51-实现报告-2026-11-04.md new file mode 100644 index 00000000..10c80207 --- /dev/null +++ b/reports/iter51-实现报告-2026-11-04.md @@ -0,0 +1,102 @@ +# 迭代 51 实现报告(2026-11-04) + +> 四卡:①EffectPort 蓄力层删键批 ②架构审查 5.1 封装修复批 ③架构审查 P2 代码面批 ④L1 深度 3 混编。 +> 工作区=g-plan2-iter51(分支 iter51,基点 63fe2da0);提交序列见 git log(卡① 一笔/卡② 四笔 T1+T2、T3+T4、T5、——/卡③ 一笔/卡④ 一笔)。 +> **卡② 停卡呈报在案(S13 一键待裁)**——见「六、停卡呈报」,合并等用户裁决。 + +## 一、各卡状态 + +| 卡 | 状态 | 提交 | 卡内验证读数 | +|---|---|---|---| +| ① EffectPort 蓄力层删键 | 完成 | ace15701 | effect_port_test 16 checks(含新 V4)/treasure_effects PASS/refresh_feats 85 checks;零残留 grep 达成(例外见决定记录 2);S13 零新违规 | +| ② 5.1 封装修复 | **施工完成·S13 停卡呈报待裁** | 568c474d/d43de922/0e1763bd | golden 主证改前固化改后逐位同(seed 4242,3 checks)/settle_segments 18 checks/adventure_feats 138 checks/battle_settlement 七套件绿/存档五套件绿(17+97+22+13+29)/sweep 38 checks/冒烟绿;**S13 新违规 1 条**(minesweeper_rules 公开方法 21>20 贴顶——§六) | +| ③ P2 NODE_TYPES 单点化 | 完成 | 52bb63d7 | encapsulation_p2_sweep 6 checks/node_map_determinism 21 checks(同种子逐位——零行为变更主证)/冒烟绿;S13 键零变化 | +| ④ L1 深度 3 混编 | 完成 | afaab6a7 | table_structure_guard 83 checks(v1–v6)/round_flow 136 checks(含 _smoke_enemy_buff_targets_self 种子搜索命中)/level_shop 195 checks/damage_controller 50/battle_narrator 22/config_db_test 29/config_db_resolver 13;全仓 hobgoblin 零残留(例外见决定记录 7);Alpha 零读数(静态论证) | + +## 二、实际改动—验收对照表 + +### 卡①(对照表 3 行全绿) + +| # | 需求点 | 改动 | 验收测试(落点+尾缀) | 真实入口 | 判据绑定面 | 结果 | +|---|---|---|---|---|---|---| +| 1 | 事实应答面收窄 | battle_fx_host.gd biz_fact/_fx_fact 两分支删 | effect_port_test.gd:131「V4 闭面收窄」 | begin_battle 真实装配链取宿主 biz_fact | 键在→返 0、键退役→null | 绿(16 checks 内) | +| 2 | 账本与转蓄分支退役、签名简化 | action_economy.gd finish_round_stop 纯 RAP 转存+grant_segment 调用简化 | treasure_effects_test.gd ⑥ 三断言(132/138/144 调用单参化,期望值零变) | request_stop→round_end→finish_round_stop 真实停手路径 | RAP 转存读数 4/5/3 逐位零漂移 | 绿(PASS) | +| 3 | 事件/事实两词表位退役 | effect_engine.gd EVENTS/FACTS 删位 | 静态判据+零残留 grep+S13+--code 域(合回 CI) | —(词表常量面) | 静态判据+评审注记(申报观测面局限在卡) | 绿(grep 零命中) | + +### 卡②(对照表 R1–R6 全绿,全部「非行为改动」行) + +| # | 需求点 | 改动 | 验收测试 | 真实入口 | 判据绑定面 | 结果 | +|---|---|---|---|---|---|---| +| R1 | 七段解耦+束交付 | 七文件重写+编排器 20 键束+三调用点 | settlement_wiring_snapshot_test〔封装5.1 V1〕+settle_segments_test 束构造/同一性断言+round_flow/seal_polarity/refresh_feats 既有全套 | begin_battle→settle_batch→player_request_refresh 全公开入口 | 同种子行流逐位同 golden+束引用同一性+\boc\b 七文件零命中 | 绿 | +| R2 | 搬家五方法归位 | damage_controller +hero_atk/hits_of/emit 公开化+facts;reward_grant +feat_yield_adjust;minesweeper +harmful_variants/harmful_adjacent;编排器五方法删 | golden V1(五方法消费路径全在行流内)+roll_multiplier_test 既有+sweep V3 | begin_battle/授予段真实结算链 | 行流逐位同+五方法宿主定义唯一 | 绿 | +| R3 | grant→BattleFxHost 累积方法 | battle_fx_host +mark_new_resource_type/+add_bonus_delta;grant 四处改址 | seal_polarity/round_flow 既有回归+sweep V6 | 授予段真实结算链 | bonus_delta 轴累积与首见判定逐位同+grant 源零私键 | 绿 | +| R4 | adventure_runner→BreakpointStore | breakpoint_store +13 公开方法(bp_read 公开化);adventure_runner 37 处改址 | save_rng_roundtrip_test 两断言既有+sweep V4 | resume_adventure/start_adventure→write_to_disk/load_from_disk | 断点续玩逐位同+源零私取 | 绿 | +| R5 | save_manager↔SlotStore 双向 | slot_store +active_path/migrate 公开化+21 处改址;save_manager +5 公开方法/2 更名+九行改址 | save_manager_domain_test+save_slot_isolation_test+sweep V5 | write_to_disk/load_from_disk/_ready 迁移口/activate_slot/peek_slot_summary | 写盘读回迁移激活窥视逐位同+双向源零私取 | 绿 | +| R6 | config_db→三表挂载 | 三 schema +bind_resolver;config_db 三行改址 | config_db_resolver_test 既有全套+sweep V7 | ConfigDB.load_tables→resolve_* | 行集解析与难度 clamp 逐位同+源零直写 | 绿 | + +**零行为变更证明**:T1 golden 改前基线固化(seed 4242 未改树转储 7444 字符 JSON 填入常量)、改后全程绿=行流/快照/终局载荷/presentation 逐位同;既有套件零转红;sweep 恒零。 + +### 卡③(对照表 R1 全绿) + +| # | 需求点 | 改动 | 验收测试 | 真实入口 | 判据绑定面 | 结果 | +|---|---|---|---|---|---|---| +| R1 | NODE_TYPES 单点化 | node_map_generator :10 删+:135 改 LevelTable.NODE_TYPES.has | encapsulation_p2_sweep_test〔架构P2 V1/V2〕+node_map_determinism_test 既有全套 | LevelRunner 关卡推进→节点图生成;LevelTable 表行加载校验 | 词表定义唯一落点(正锚计数=1)+生成链同种子逐位同 | 绿 | + +### 卡④(对照表 R1–R10 全绿) + +| # | 需求点 | 改动 | 验收测试(尾缀〔L1混编 vN〕) | 真实入口 | 判据绑定面 | 结果 | +|---|---|---|---|---|---|---| +| R1 | 行数与键序 | enemy.tres (1,3) 一行换两行 | table_structure_guard「敌人表 29 行且键序=覆盖表序((1,3) 两行相邻、熊地精在前)」 | ConfigDb.load_tables→EnemyTable.rows | 遍历 rows 拼四段串逐行比对 | 绿(83 checks 内) | +| R2 | 混编构成与行序 | 同上 | 同文件「(1,3) 行集恰两行、enemy_id 序列==[bugbear,goblin]」 | resolve_rowset(1,3,"battle") | 返回长度与 enemy_id 序列 | 绿 | +| R3 | 静态最坏骰和 | 同上 | 同文件「Σ(unit_count_max,固定行缺省)==3」 | 同上 | 求和值==3 | 绿 | +| R4 | 行值整行复用 | 同上 | 同文件「(1,3) 两行逐字段==(1,2)/(1,0) 行」 | EnemyTable.rows | 字段逐项比对零字面量 | 绿 | +| R5 | 零残留 | 表+七文件 | 同文件「表内 enemy_id 集合不含 hobgoblin」+T2 回读命令 | rows 遍历+grep | 集合不含+grep 空 | 绿 | +| R6 | 混编下 spawn 数全域内 | round_flow :312-317 口径逐行求和 | _smoke_encounter_roll 全深度×12 种子扫描 | begin_battle→presentation_snapshot().enemies.size() | 逐场数量∈[Σunit_count,Σunit_count_max] 且 ≥1 | 绿(136 checks 内) | +| R7 | 双池各自独立 | 表两行 | _smoke_skill_pool_data「(1,3) 两行池模板」 | 表读取 | 键与权重逐项 | 绿 | +| R8 | 注入段还原行数 | level_shop :727 | _smoke_level_graph「表回 29 行」 | 同套件注入/还原流程 | rows.size()==29 | 绿 | +| R9 | 深度 4/5 落带不变 | 表(零触碰) | table_guard「(1,4)/(1,5) battle 行集==单行 bugbear(band 6)」 | resolve_rowset(1,4/5) | 长度与 enemy_id、band 归属 | 绿 | +| R10 | 深度 3 最少 2 敌(回归护栏) | 两处夹具注释 | damage_controller_test _test_pool_consumption_per_unit+battle_narrator_test _test_snapshot_effects_per_unit 在混编行集下仍绿 | UnitContainer.spawn(1,3) | all_enemies().size()≥2 | 绿 | + +## 三、S13 对账 + +- 改前基线:gdstyle_gate 5 键基线内、基线外 0(T1 现场读数;adventure_runner 类变量 23 照登)。 +- 改后:**基线外新违规 1 条=game/core/minesweeper_rules.gd|quality/max-public-methods(21>20 贴顶)**——卡② 停卡呈报项(§六)。其余全部触件 S13 键只降不升:action_economy 删 2 成员、七段束解包全在预算内(grant 12/combat 10/verdict 5/aura 1/decay 2/round_cycle 15 贴顶/battle_refresh_flow 8)、breakpoint_store 类变量 15 贴顶方法-only、编排器 15 不变。 + +## 四、实机目检总清单(真人检测只登记不阻塞) + +- 卡①②③:无实机目检项(死码删除/封装收口/词表单点化——零玩家可观测提交面;卡② 零行为变更由 golden 逐位对拍承载)。 +- 卡④(抄体验验证清单 ⑱ L1 节点 3 三项):① 混编遭遇实机目检(L1 节点 3 遇敌=熊地精 1+地精 1–2,默认目标=熊地精);② 开战叙述首行=熊地精;③ 战斗结算面两种数量语义并存目检。读数义务:Alpha 零读数(2026-11-04 用户裁),带位移读数归数值统一修正批。 + +## 五、能力清单核对 + +game/tests/README.md:本批零编辑(四卡对照表能力行全部「既有」;测试函数名零改名——settle_segments/round_flow/level_shop/damage_controller/battle_narrator 代表用例名在册不变;卡② 新增两测试件按「经宿主链覆盖」申报=snapshot 测试挂 R1 行 begin_battle 全结算链、sweep 挂各 R 行源扫描)。名实核对一致,无回表项。 + +## 六、停卡呈报(卡② S13 一键——实现段零裁断,缺陷移交) + +**分歧点**:minesweeper_rules.gd 公开方法数。写卡 §14 申报「18→20 贴顶(+2)」;master 基点实数 **19**(`git show master:game/core/minesweeper_rules.gd` 逐 func 清点),本批 +2 搬家方法(harmful_variants/harmful_adjacent)后 **21>20**(S13 默认阈值)——gdstyle_gate 基线外新违规 1 条。 + +**两种读法**: +- 读法 A(写卡考证误差 1,预算按实况校准为 19→21):+2 是卡② 五方法归位裁决的直接后果(grant_segment :87/:339 两消费方直调,两方法都必须公开,少任何一件破坏搬家合同);21 超阈值需用户特批准记基线(迭代 38 先例 c4c69701——S13 恶化经用户特批准记入基线存量红)。但 static_scan S13 头注「登记值只准随还债下降,禁抬高到现状」——特批是唯一合法通道,闸件纪律不因批内需要自动放行。 +- 读法 B(卡面预算准、实现走形):不存在第三形态——拆函数降计数=实现段自行拆件(L3 ③禁);改闸件豁免=禁(卡② §13;先例迭代 38 卡② 停卡);把方法挪去 BoardData 或他件=职责面重设计(上游裁决)。 + +**建议案**:读法 A——用户特批准记基线 1 键(minesweeper_rules 公开方法 21),理由:+2 归位是 2026-11-03 会话裁决的机械承载、消费面决定两方法必须公开、基点考证误差 1 属写卡段缺陷(非实现超预算)、特批有迭代 38 先例。批后可开「minesweeper_rules 公开面还债」条目(归后续批:如 settle_batch/fx_reveal 族收敛)随还债降键。 + +**该红不阻塞的实证**:卡② 全部验收断言绿、golden 零漂移、sweep 恒零——S13 是结构预算闸不是行为闸;红项单键、修复路径已指认(特批记基线或后续还债批)。用户裁决后本迭代即可走独立验收审计→合并;若用户裁读法 B(拆面),开修复 worktree `fix-iter51-s13` 处置后重审计。 + +## 七、实现决定记录(代拍板白名单外备案) + +1. **卡① has_effect_on 零消费方保留**:删 grant_segment 调用后 `EffectEngine.has_effect_on` 全仓零调用。卡面 :203 处置=注释改写、函数保留(T0 步骤明写「:203 句改……」未列函数删除),未代裁扩大删除面。后续可另立待办处置(纯框架 API 死码)。 +2. **卡① 零残留 grep 唯一例外**:effect_port_test.gd:131 V4 断言文案与 `biz_fact("charge_layers")` 参数引证退役键名——卡面 T0 定死的断言文案(证明「键退役→null」必须引用键名),断言优先于 grep 字面。 +3. **卡① 三处 swap 域描述词换代**:run_all.gd:213 manifest 注释、effect_port_test :67/:83 的「蓄力层」简称改「蓄力周转」(swap_charge_layers 在役域,词面清旧称;断言判据零变——:83 属断言文案措辞,判据 `v_c - v_0 >= 1` 不动)。 +4. **卡② oc._ 计数 102 vs 卡面 105**:迭代 50 甲案删 attack_segments 分支所致(卡面预告的漂移,grep 符号锚定位,禁改判据)。 +5. **卡④ 曲线护栏逐带累加**:table_structure_guard_test 第一关总量比断言原单行覆盖口径在 (1,3) 双行同带下失真(后行覆盖前行→band3 总量 12→72 比值 6.00 假红);改同带逐行累加(band3=22×1+8×1.5=34,比值 2.12)——与卡④ R6 已裁「逐行求和与 spawn 守卫同口径」同构,阈值 2.5 零变化,非放宽。期望序列文案 12/10/22/35/72→12/10/22/34/72(34=混编实况)。 +6. **卡④ level_shop 白盒抬 GP**:(1,3) 混编改战斗流随机消费序→_smoke_shop_pool 种子扫描选中的场景 gp 分布漂移(进店刷新扣费后 gp=1<全架最低价)→「可购卡在架」假红。修法=③ 买段前白盒 `r8.gp += 128`(A 银段 :1345 同款先例「直改测试基建——非生产行为」),买链判据零变化;MC-1 刷新段保持原位(进店未买态语义),未动段落序。 +7. **卡④ R5 零残留 grep 唯一例外**:table_structure_guard_test 新增 v5 断言文案与 `id_set.has("hobgoblin")` 引证退役键名(卡面 §八 R5 定死验收文案)。 +8. **卡② 测试连带三处迁移**(写卡 §3.2 漏盘点):adventure_feats_test :791 与 level_shop_economy_test :1484 `a._bp_store._pending_next_level` → `pending_next_level()`;feat_vouchers_test :81 `_loadout_seeded = false` → `set_loadout_seeded(false)`(断点公开面在用,引用形态迁移非削弱);damage_controller_test :55 直构夹具补 BattleFacts 第五参(facts 构造注入——hero_atk 消费面,夹具空 encounter=表值缺省)。**审计订正**:level_shop 一处曾随回滚实验丢失(卡④ 提交后由独立验收审计查出,:1484 补迁移复跑 195 checks 绿——动态访问失效成员使停点分支恒假、依赖种子被扫描跳过掩盖红,属申报落实的漏项非削弱)。 +9. **卡② snapshot 测试对拍形态**:GOLDEN 三引号 raw string 首尾各带换行(多行字符串语法载体),比较两侧 strip_edges 后逐位等——判据零变(内容逐位),语法载体适配。 + +## 八、遗留读数义务与反哺点 + +- 遗留读数:卡④ Alpha 零读数(归数值统一修正批);卡② S13 一键(§六待裁)。 +- 反哺点:见 `reports/iter51-反哺点-2026-11-04.md`(architecture.md 十处+GDD/术语确认行)。 +- 待办清单条目状态推进与 roadmap「当前位」换代:归文档批(本批禁写)。 diff --git a/reports/iter51-验收审计-2026-11-04.md b/reports/iter51-验收审计-2026-11-04.md new file mode 100644 index 00000000..3cdafe66 --- /dev/null +++ b/reports/iter51-验收审计-2026-11-04.md @@ -0,0 +1,190 @@ +# 迭代 51 独立验收审计(2026-11-04) + +> 审计身份:独立会话审计员,未参与本批实现(实现由另一会话完成)。 +> 工作区:D:\code\g-plan2-iter51(分支 iter51,基点 master=63fe2da0,diff 范围 63fe2da0..HEAD,HEAD=ef3cd8db,工作树 clean)。 +> 审计范围:四张需求卡片(①EffectPort 蓄力层删键批 ②架构审查 5.1 封装修复批 ③架构审查 P2 代码面批 ④L1 深度 3 混编)+实现报告 §一–§八+全量 diff 逐文件核对(48 件,+1594/−474)。 +> **验收档:四卡均未申报验收档——按高档全量执行**(对照表逐行核对+判据绑定面抽查+最窄测试复跑抽查+逐处削弱检查+直查例外核对+S13 对账复核)。 +> 纪律:只读生产码与测试;禁变异类测试、禁撤销类取证;本报告为本审计唯一写入文件。 + +## 一、最窄测试复跑读数 + +环境:Godot 4.7.2 console 版(D:\code\godot\Godot_v4.7.2-stable_win64_console.exe)headless,工作目录 game/,`--script` 单件直跑;跑前确认 `game/.godot/global_script_class_cache.cfg` 在位(直接跑)。退出码 0=绿。 + +| 套件 | 归属 | 退出码 | 读数 | +|---|---|---|---| +| effect_port_test | 卡① V4(五件套) | 0 | all green(16 checks,V4 断言在列全 PASS) | +| settlement_wiring_snapshot_test | 卡② R1 golden 主证(五件套) | 0 | all green(3 checks,golden 逐位同) | +| encapsulation_sweep_test | 卡② V2–V7(五件套) | 0 | all green(38 checks) | +| encapsulation_p2_sweep_test | 卡③ V1/V2(五件套) | 0 | all green(6 checks) | +| table_structure_guard_test | 卡④ v1–v6(五件套) | 0 | all green(83 checks,v1–v6 六条全 PASS) | +| round_flow_skill_pool_test | 审计自选(卡④ R6/R7/R10) | 0 | all green(136 checks,含 buff_targets_self 种子搜索命中 seed=1) | +| save_rng_roundtrip_test | 审计自选(卡② R4) | 0 | all green(17 checks) | +| treasure_effects_test | 卡① 行 2 | 0 | PASS | +| settle_segments_test | 卡② R1 | 0 | all green(18 checks) | +| adventure_feats_test | 卡② R2 | 0 | all green(138 checks) | +| seal_polarity_test | 卡② R3 | 0 | all green(70 checks) | +| refresh_feats_test | 卡① 契约 2/卡② R1 | 0 | all green(85 checks) | +| roll_multiplier_test | 卡② R2 | 0 | all green(42 checks) | +| config_db_resolver_test | 卡② R6/卡④ | 0 | all green(13 checks) | +| config_db_test | 卡④ | 0 | all green(29 checks) | +| save_manager_domain_test | 卡② R5 | 0 | all green(97 checks) | +| save_slot_isolation_test | 卡② R5 | 0 | all green(22 checks) | +| feat_vouchers_test | 卡② 连带迁移 | 0 | all green(49 checks) | +| damage_controller_test | 卡④ R10 | 0 | all green(50 checks) | +| battle_narrator_test | 卡④ R10 | 0 | all green(22 checks) | +| level_shop_economy_test | 卡④ R8 | 0 | all green(195 checks) | +| node_map_determinism_test | 卡③ 零行为主证 | 0 | all green(21 checks) | + +## 二、卡① EffectPort 蓄力层删键批 + +### 对照表逐行核对(3 行) + +| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | +|---|---|---|---|---| +| 1 | effect_port_test.gd:131 断言在(V3 组 `_test_biz_fact_face_closed` 末尾) | 「V4 闭面收窄:charge_layers 键退役——宿主事实应答 null」grep 命中 :131 | begin_battle 真实装配链(:102 `orch.begin_battle(_enc([]))` 受理后 :131 取 `orch.fx_host().biz_fact("charge_layers")`——fx_host() 公开访问器+biz_fact 公开方法,未绕过装配链直调下层) | 通过 | +| 2 | treasure_effects_test 三断言在,期望值 rap==4/5/3 逐字未动(diff 仅调用实参行) | 无新增尾缀(既有断言回归锚,卡面即如此定) | request_stop→finish_round_stop 真实停手路径;直构 ActionEconomy 夹具=卡面 §8 测试方式两分法明示的调试接口验证形态,且卡面 §7 T0 逐字定死该三行改单参——非绕过 | 通过 | +| 3 | 静态判据:effect_engine.gd EVENTS `charge_full` 位、FACTS `charge_layers` 位删除(diff 证实 :14/:59 两处删位) | —(词表常量面,卡面明示无独立运行期提交面) | —(静态判据+评审注记,卡面申报观测面局限在案) | 通过 | + +零残留复核:`grep -rn "charge_layers|charge_full|CHARGE_LAYER|蓄力层|转蓄力|厚积薄发|铁壁|整备军备" game/ --include=*.gd --include=*.tres` 全部命中仅在 swap_charge_layers 在役域(feats.tres :1018-1056 六行、refresh_feats_test `test_charge_layers_consumed_same_round` 函数名与其断言、effect_port_test :16 注释)+V4 断言键名引证(effect_port_test :131)——前者=卡面 §1 完成定义明写豁免域,后者=报告决定记录 2 申报的唯一例外(证明「键退役→null」必须引用键名)。达卡面 T1 零残留判据。 + +### 削弱检查(逐处) + +| diff 处 | 申报出处 | 结论 | +|---|---|---| +| treasure_effects_test 三处 finish_round_stop(false,N)→(N)(:132/:138/:144) | 卡① §8 测试削弱申报 | 通过——断言期望值与检查语义逐字零变化 | +| effect_port_test :57 注释+:83 断言文案「蓄力层→蓄力周转」 | 卡① §8 申报(V1 两处文案) | 通过——判据 `v_c - v_0 >= 1` 不动 | +| effect_port_test :16/:103 误标注释改写 | 卡① §7 T1 申报(三处注释) | 通过——注释级 | +| run_all.gd:213 manifest 注释「蓄力层→蓄力周转」 | 报告决定记录 3 | 通过——注释级,判据零变 | +| has_effect_on 函数保留(grant_segment 调用删后全仓零调用方) | 报告决定记录 1(卡面 T0 :203 只列注释改写、未列函数删除) | 通过——未代裁扩大删除面,与卡面一致 | + +无未申报删改。 + +### 直查例外核对 + +对照表三行均未声明「内部字段直查=受控例外」:行 1 走公开面(fx_host()/biz_fact);行 2 直构夹具=卡面 §8 两分法明示形态;行 3 静态面。无需例外注记,判定=通过。 + +## 三、卡② 架构审查 5.1 封装修复批 + +### 对照表逐行核对(R1–R6) + +| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | +|---|---|---|---|---| +| R1 | settlement_wiring_snapshot_test.gd:456 主对拍在(GOLDEN 常量 :22-416 固化,seed 4242;dump 通道 :443-448);settle_segments_test :30-34 束构造 5 条+:38-44 同一性 6 条在 | 「封装5.1 V1」:4/:456 命中 | begin_battle→minesweeper.settle_batch→player_request_refresh 全公开入口;段类消费全经真实编排器接线(settle_segments 直构束=卡面 §8 申报 1 的适配形态) | 通过 | +| R2 | golden V1 覆盖五方法消费路径(主角攻击 base_value/奖励乘区/terrain_kind 事实/hero_atk override 行均在 golden 行流内);roll_multiplier_test 既有回归绿;sweep V3 编排器五方法定义零命中 | sweep :69-70 五方法名零命中断言(〔封装5.1〕尾缀 :36/:42 模板命中 38 处) | begin_battle spawn 经 `_damage.hero_atk()`(battle_orchestrator.gd diff :285 证实改指),未绕过 | 通过 | +| R3 | seal_polarity_test 70 checks/round_flow 136 checks 绿+sweep V6(grant 两私键零命中+mark_new_resource_type 正锚 :100-101) | 〔封装5.1〕尾缀在案 | 授予段真实结算链(两套件全走 begin_battle) | 通过 | +| R4 | save_rng_roundtrip_test diff 零改动(未触碰)、17 checks 绿;sweep V4(adventure_runner `_bp_store\._` 零命中+`_bp_store\.bp_read\(` 正锚 :78-79) | 〔封装5.1〕尾缀在案 | AdventureRunner.resume_adventure/start_adventure→write_to_disk/load_from_disk(integration 件真实入口) | 通过 | +| R5 | save_manager_domain_test 97/save_slot_isolation_test 22 绿;sweep V5(save_manager `slots\._` 零命中+active_path 正锚;slot_store `_save\._` 零命中+七公开面正锚 :84-92) | 〔封装5.1〕尾缀在案 | write_to_disk/load_from_disk/_ready 迁移口/activate_slot/peek_slot_summary(两套件承载) | 通过 | +| R6 | config_db_resolver_test 13 checks 绿(rowset 保序/行类路由/难度 clamp);sweep V7(config_db `\._resolver\s*=` 零命中+`bind_resolver\(_resolver\)` 正锚 :109-110) | 〔封装5.1〕尾缀在案 | ConfigDB.load_tables→resolve_*(三 schema bind_resolver 类型环当轮真实跑通——T4 义务承载) | 通过 | + +零行为变更主证复核:golden 对拍断言绿(本审计复跑);段类对编排器引用归零由 sweep V2 七文件 `\boc\b` 零命中承载;束 20 键组装与五方法删除、RoundCycle/BattleRefreshFlow 形态换代在 diff 逐文件证实。 + +### 削弱检查(逐处) + +| diff 处 | 申报出处 | 结论 | +|---|---|---| +| settle_segments_test :20-24 五段直构断言改束构造成功(:30-34) | 卡② §8 申报 1 | 通过——段类可独立实例化断言意图保持 | +| settle_segments_test :28-34 宿主回指 6 条改注入同一性(:38-44——round_cycle.facts/grant.facts/aura 回调对象 get_object()==oc/combat.fx_host/decay.status_effects/verdict.units) | 卡② §8 申报 2 | 通过——6 条全数对上,强度对应回指 | +| adventure_feats_test :671/:691/:727 三处 orch._hits_of→DamageController.hits_of | 卡② §8 申报 3 | 通过——断言内容(attack_bonus 命中过滤)零改动 | +| adventure_feats_test :791 `a._bp_store._pending_next_level`→`pending_next_level()` | 报告 §七 8 | 落地证实(grep 现值为公开方法调用) | +| feat_vouchers_test :81 `_loadout_seeded = false`→`set_loadout_seeded(false)` | 报告 §七 8 | 落地证实 | +| damage_controller_test :56 直构夹具补 BattleFacts 第五参 | 报告 §七 8 | 通过——夹具空 encounter=表值缺省,断言零改动 | +| snapshot 测试 strip_edges 对拍(:449-451) | 报告 §七 9 | 通过——内容逐位判据零变,三引号语法载体适配 | +| **level_shop_economy_test :1484 `a._bp_store._pending_next_level`——报告 §七 8 与卡④ commit信息均申报为已迁移,实际未改** | —— | **申报失实,见第六节;非削弱(该行零改动=零删改)** | + +无未申报删改、无阈值放宽、无扫描面缩小。 + +### 直查例外核对 + +settle_segments_test 直查 `oc._facts`/`oc._rounds._grant_seg.*` 等编排器内部(同一性断言)=卡② §5 明示受控例外(「sim/probe/tests 的测量面直构惯例与测试受控例外——零清扫:orch._facts/orch._rounds/orch._emit_row 等测试直连维持现状」)。报告 §七 8 三处测试直连迁移=附加申报(其中一处未落地,见第六节,且该处属同一受控例外面、运行态零差异)。例外均有注记,判定=通过。 + +## 四、卡③ 架构审查 P2 代码面批 + +### 对照表逐行核对(R1) + +| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | +|---|---|---|---|---| +| R1 | encapsulation_p2_sweep_test V1(:40-41 生成器 `^const NODE_TYPES` 零命中+`LevelTable\.NODE_TYPES\.has\(` 正锚)/V2(:56-58 生产三目录 `NODE_TYPES` 定义恰 1 处且落 data/schemas/level_table.gd);node_map_determinism_test 既有全套 21 checks 绿 | 「〔架构P2〕」尾缀 :27/:32/:56/:58 命中 | 源扫描(静态)+LevelRunner 关卡推进生成链(determinism 同种子逐位——零行为变更主证) | 通过 | + +生产面独立复核:`grep -rnE "NODE_TYPES\s*:?\s*=" core data presentation` 唯一命中 data/schemas/level_table.gd:11;node_map_generator.gd 本地 const 删、:135 改 `LevelTable.NODE_TYPES.has(...)`(diff 证实);S3 schema class_name 直引豁免面形态(卡面 §3 在案)。 + +### 削弱检查 + +卡面 §8 申报零条;diff 中本卡测试改动仅新增 sweep 件+run_all manifest +1 行——零删改既有断言。通过。 + +### 直查例外核对 + +纯源扫描与既有套件回归,无内部字段直查。通过。 + +## 五、卡④ L1 深度 3 混编 + +### 对照表逐行核对(R1–R10) + +| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | +|---|---|---|---|---| +| R1 | table_structure_guard_test :232 v1(29 行键序=(1,3) 两行相邻、熊地精在前) | 〔L1混编 v1〕:232 命中 | ConfigDb.load_tables→EnemyTable.rows 遍历拼四段串逐行比对 | 通过 | +| R2 | :242 v2(行集恰两行、enemy_id 序列==[bugbear,goblin]) | 〔L1混编 v2〕:242 命中 | `resolve_rowset(1,3,"battle")` 返回序列——判据绑返回长度与 enemy_id 序列(被测数据改动特有提交面) | 通过 | +| R3 | :244 v3(Σ(unit_count_max,固定行缺省)==3) | 〔L1混编 v3〕:244 命中 | 同上求和值==3 | 通过 | +| R4 | :258 v4(两行逐字段==(1,2)/(1,0) 行,零字面量) | 〔L1混编 v4〕:258 命中 | EnemyTable.rows 字段逐项比对 | 通过 | +| R5 | :263 v5(表内 enemy_id 集合不含 hobgoblin)+T2 回读命令 | 〔L1混编 v5〕:263 命中 | rows 遍历+grep | 通过 | +| R6 | round_flow :320 全域扫描逐行求和(72 场全绿) | 断言文案「逐行求和」:320 命中 | begin_battle→presentation_snapshot().enemies.size() | 通过 | +| R7 | round_flow :575-576 池模板两键(bugbear 6/2/2+goblin 9/1/1,顺序按表行序) | :575-576 两键在案 | 表读取逐键逐权重比对 | 通过 | +| R8 | level_shop :727「表回 29 行」 | :727 断言在案 | 同套件既有注入/还原流程(rows.size()==29) | 通过 | +| R9 | :270 v6((1,4)/(1,5) battle 行集==单行 bugbear(band 6)) | 〔L1混编 v6〕:270 命中 | resolve_rowset(1,4/5,"battle") | 通过 | +| R10 | damage_controller_test _test_pool_consumption_per_unit/battle_narrator_test :114-115 _test_snapshot_effects_per_unit 混编行集下绿(50/22 checks) | —(回归护栏行,卡面即不设新尾缀) | UnitContainer.spawn(1,3)→all_enemies().size()≥2 | 通过 | + +行值整行复用独立复核:enemy.tres (1,3) bugbear 行(:82-104)与 (1,2) 行(:58-80)逐字段一致(hp22/atk1/atk 0-2/池 attack6-charge2-heavy2/normal/light/ac2/unit_count 1 无 unit_count_max);(1,3) goblin 行(:106-129)与 (1,0) 行(:8-31)逐字段一致(hp8/池 attack9-lunge1-desperate_guard1/normal/light/ac0/unit_count 1-2)。行序 bugbear 在前、两行相邻。表注四处处置(实体名修正段删/首批投放段尾追加/N1 括注保留/缺口句换新注)diff 逐处证实。零残留复核:全仓 `hobgoblin` grep(data/core/presentation/probe/sim/tests/tools)唯一命中=v5 断言自身键名引证(table_structure_guard_test :259 注释+:263 断言)=报告决定记录 7 申报的唯一例外。 + +### 削弱检查(逐处) + +| diff 处 | 申报出处 | 结论 | +|---|---|---| +| table_guard :234-241 hobgoblin 留任断言删(原〔fv12〕) | 卡④ §十二 申报① | 通过——钉已退役事实;原位换 v2–v6 五条新断言,覆盖强度净增 | +| round_flow :312-317 首行区间改逐行求和 | 卡④ §十二 申报② | 通过——与 unit_container spawn 守卫同口径,72 场全绿,双行行集下非放宽(旧口径必红) | +| round_flow :615 文案二十八行九种→二十九行十种 | 卡④ §十二 申报③ | 通过——只改文案不改判据 | +| static_scan.sh 删 FNR==891 豁免(:145 头注+:222 执行点) | 卡④ §十二 申报④ | 通过——对象行已随表注段删除,留置会误放别行 | +| 曲线护栏单行覆盖口径改逐带累加(期望序列 12/10/22/35/72→12/10/22/34/72,阈值 2.5 不动) | 报告 §七 5 | 通过——(1,3) 双行同带下旧「后行覆盖前行」口径失真(band3 总量 12→72 比值假红),新口径=同带逐行累加、与 R6 逐行求和同构;阈值零变化 | +| level_shop :1314-1324 白盒抬 GP 128+文案改 | 报告 §七 6 | 通过——测试基建直改(A 银段 :1345 同款先例在案),买链判据零变化 | +| config_db_resolver_test 合成行 hobgoblin→giant_spider(:44,三条期望串同步) | 卡④ §七 T2 明写(并明写不换 bugbear 的理由) | 通过——夹具级,断言结构零变 | +| config_db_test :222 「不相邻」样本换 giant_spider | 卡④ §七 T2 明写 | 通过——同上 | +| damage_controller_test :414/battle_narrator_test :114 两处夹具注释改述 | 卡④ §七 T2 明写(断言零改动) | 通过 | +| doc_term_audit.py :144 注释敌种枚举去 hobgoblin | 卡④ §七 T2 明写 | 通过——注释级 | + +无未申报删改、无阈值放宽、无未申报扫描面缩小。 + +### 直查例外核对 + +卡④ 判据全部绑公开面(rows/resolve_rowset 返回序列/presentation_snapshot/UnitContainer.spawn)——无需例外注记。通过。 + +## 六、申报失实项(非验收缺漏,需订正) + +**实现报告 §七 第 8 条与卡④ commit afaab6a7 提交信息均申报「卡②断点公开面三处测试直连迁移(pending_next_level×2/loadout_seeded)」,实际落地两处:** + +- adventure_feats_test :791 `a._bp_store.pending_next_level()`——已落地; +- feat_vouchers_test :81 `a._bp_store.set_loadout_seeded(false)`——已落地; +- level_shop_economy_test :1484 `if a._bp_store._pending_next_level:`——**未改**(该文件 diff 全量核对仅 :724-727 与 :1314-1324 两处触碰)。 + +定性与影响:非验收缺漏——该行零改动=无断言删改、无削弱;测试侧 `_bp_store._` 直查属卡② §5 明示受控例外面;BreakpointStore `_pending_next_level` 成员保留(breakpoint_store.gd:60),运行态与改前逐位同,套件全绿(level_shop 195 checks)。但实现报告与提交信息的该条申报与实况不符,构成产出物事实错误——建议订正实现报告 §七 第 8 条(改为「两处落地、level_shop 一处未迁」);commit 信息不可改,以本节记录在案。 + +## 七、S13 对账复核 + +实测 `python tools/gdstyle_gate.py`(工作目录 game/,退出码 1): + +``` +[gdstyle_gate] 扫描面 158 个 .gd;命中 6 条,基线内 5 条,基线外新违规 1 条,较登记值恶化 0 条 + FAIL 新增 game/core/minesweeper_rules.gd|quality/max-public-methods +[gdstyle_gate] FAIL — 基线外新违规 1 条/恶化 0 条——新码须修到基线口径内,恶化须还债或经用户批准随批重生成基线 +``` + +与实现报告 §六 逐字一致。独立考证:基点 63fe2da0 的 minesweeper_rules.gd 公开方法实数 19(`git show 63fe2da0:game/core/minesweeper_rules.gd` 逐 func 清点);HEAD 21(含新增 harmful_variants/harmful_adjacent 两搬家方法,diff :58/:70 证实)→ 21>20(S13 默认阈值)。其余触件只降不升(action_economy 类变量 14→12 实测;gdstyle 无第二键恶化)。 + +该项为实现报告 §六已停卡呈报待用户裁的结构闸项(写卡考证 18 失实、基点实数 19、+2 搬家方法),**不属于验收闭合缺陷**——本审计如实记录其状态与呈报位置,不计入审计终判。**合并前置条件=用户对该键的裁决**(特批准记基线随还债降键,或裁实现报告 §六读法 B 开 fix-iter51-s13 修复批后重审计)。 + +## 八、终判 + +**通过**——四卡对照表全部行(卡① 3 行/卡② R1–R6/卡③ R1/卡④ R1–R10)验收断言真实存在、尾缀可 grep 定位、真实入口未绕过被测改动点、判据绑被测改动特有的可观测提交面;最窄复跑五件套+审计自选两件+对照表引用全部其余套件绿(22 件全 0 退出码);测试削弱全部落在四卡申报清单与实现报告决定记录内,零未申报删改;直查例外均有注记;卡① 蓄力层族与卡④ hobgoblin 零残留 grep 达成且例外均为申报例外。 + +记录在案三项(均不改变终判): + +1. **S13 一键**(minesweeper_rules 公开方法 21>20)——停卡呈报在案(实现报告 §六)。**合并前置条件=用户对该键的裁决**;裁决后按既定通道走。 +2. **申报失实一项**——实现报告 §七 第 8 条「三处测试直连迁移」实际落地两处(level_shop_economy_test :1484 未迁),建议订正报告;非验收缺漏、非测试削弱。 +3. 卡② golden 改前固化读数以 GOLDEN 常量在案(settlement_wiring_snapshot_test.gd:22-416,seed 4242),改后对拍绿——对拍读数在案义务已履行。 diff --git a/tools/doc_term_audit.py b/tools/doc_term_audit.py index fbd7ee46..6cc2e033 100644 --- a/tools/doc_term_audit.py +++ b/tools/doc_term_audit.py @@ -141,7 +141,7 @@ TERM_CONTEXT_EXEMPT = { "雾格": re.compile(r"不做清单|战争迷雾类机制"), # heavy_multiplier 字段与 ambusher 敌人行同于 2026-09-20 八型批整体退役(enemy_table.gd # 与 enemy.tres 均零命中);battle.md「敌人意图线索映射目录」是 2026-09-12 成文占位表, - # 现行敌人为 goblin/hobgoblin/bugbear/giant_spider/drider/duergar 系,占位行里的退役 + # 现行敌人为 goblin/bugbear/giant_spider/drider/duergar 系,占位行里的退役 # 敌人 id 只能读作历史留档(真正的现行规格在 敌人系统.md「目标顺位与意图线索」节)。 "伏击": re.compile(r"ambusher|成文占位"), "heavy_multiplier|重击倍率": re.compile(r"ambusher|heavy_multiplier|成文占位"), diff --git a/tools/gdstyle_baseline.txt b/tools/gdstyle_baseline.txt index a5216e38..d1d6b45e 100644 --- a/tools/gdstyle_baseline.txt +++ b/tools/gdstyle_baseline.txt @@ -8,5 +8,6 @@ game/core/adventure_runner.gd|quality/max-class-variables = 23 game/core/level_runner.gd|quality/max-class-variables = 30 game/core/level_runner.gd|quality/max-public-methods = 33 +game/core/minesweeper_rules.gd|quality/max-public-methods = 21 game/presentation/views/board_view.gd|quality/max-class-variables = 31 game/presentation/views/board_view.gd|quality/max-public-methods = 27 diff --git a/体验验证清单.md b/体验验证清单.md index 469421cb..782a0223 100644 --- a/体验验证清单.md +++ b/体验验证清单.md @@ -482,6 +482,8 @@ ## ㊶ 迭代 43——战斗一屏样张批方向稿目检(2026-10-30 随批后收尾登记) +> **✅ 已核销(2026-11-04)**:用户会话裁定三项按通过处理(未逐项呈报读数)——样张整体过闸成立,本批无缺陷呈报;连带 ㉘ 第 5 项群伤暗档 provisional 观感复核一并了结。回流落点见 art-bible §1.4 样张登记句与 08-Alpha版本标准「五 · 表现」两行。 + > 单卡合回(2026-10-30,git 提交 3b66d1a6):战斗一屏样张批——五项实装定值(血条危险红/条底收常量/顺位徽标提档/护甲三枚 24×24 PNG/名字行素底牌不压地台+地台件九宫格接装)+样张脚本扩展(两版画布出口、整屏四项与战斗表现五项机检 30/30 全绿、单元套件 18 checks 全绿)。**方向稿目检先行**(art-bible §1.4 闸形)——机检已全绿,气质终审通过才算样张整体过闸;通过前终稿资产与动画设计不启动。登记不阻塞(2026-10-12 真人检测立规);读数与论证详见实现报告(git 提交 3b66d1a6 一线可考)。 | # | 目检项 | 判据 | 材料 | diff --git a/待办清单.md b/待办清单.md index d9887e14..bcc2bd97 100644 --- a/待办清单.md +++ b/待办清单.md @@ -170,7 +170,7 @@ #### 站桩帧动画——Alpha 定版闸前完成(2026-10-06 登记,来源=审计发现) -**实况**:审计报告 P0-3——定稿立绘零帧动画;现行动画全部程序化(死亡碎裂/受击闪白/意图徽标弹入=程序 tween,非帧资产)。用户裁定**动画是较往后的优化,Alpha 版本定版时间点之前完成即可**——当前 Alpha 开发期达成「看上去可游玩」即可。路线未拍板,审计报告三路线供重启时裁决:①**拆件驱动**(报告推荐:定稿立绘切部件〔头/躯干/臂/腿/武器〕+ 节点层级 tween 驱动——零身份漂移、帧数无限、内存小;项目已有先例=shovel_swing 三帧 + 程序化死亡碎裂)②AI 关键姿势 + 人工修 + 程序补帧 ③全 AI 逐帧(不推荐:身份漂移/像素抖动)。前置依赖=「立绘→派生形态」管线(等比整数缩放 + 降色 ≤32 + 脚底基线对齐 + 命名合规)。**2026-11-04 用户裁改排 Beta**:本条移出 Alpha 定版前置清单,Alpha 定版不再以它为闸,路线三案与上述前置管线依赖原样留到 Beta 立项再裁(取代 2026-10-06 登记与 2026-10-13 补裁的「定版前完成最小版/未完成则顺延 Beta」口径;标题「Alpha 定版闸前完成」系登记时点原文,按登记元数据禁改条款保留)。 +**实况**:审计报告 P0-3——定稿立绘零帧动画;现行动画全部程序化(死亡碎裂/受击闪白/意图徽标弹入=程序 tween,非帧资产)。用户裁定**动画是较往后的优化,Alpha 版本定版时间点之前完成即可**——当前 Alpha 开发期达成「看上去可游玩」即可。路线未拍板,审计报告三路线供重启时裁决:①**拆件驱动**(报告推荐:定稿立绘切部件〔头/躯干/臂/腿/武器〕+ 节点层级 tween 驱动——零身份漂移、帧数无限、内存小;项目已有先例=shovel_swing 三帧 + 程序化死亡碎裂)②AI 关键姿势 + 人工修 + 程序补帧 ③全 AI 逐帧(不推荐:身份漂移/像素抖动)。前置依赖=「立绘→派生形态」管线(原稿像素化后入包、禁再降采样 + 降色 ≤32 + 脚底基线对齐 + 命名合规;显示缩放走 art-bible §8.2 乙类口径=档高÷源高、NEAREST、允许非整数倍)。**2026-11-04 用户裁改排 Beta**:本条移出 Alpha 定版前置清单,Alpha 定版不再以它为闸,路线三案与上述前置管线依赖原样留到 Beta 立项再裁(取代 2026-10-06 登记与 2026-10-13 补裁的「定版前完成最小版/未完成则顺延 Beta」口径;标题「Alpha 定版闸前完成」系登记时点原文,按登记元数据禁改条款保留)。 **缺口**:还差 Beta 立项批的路线正式拍板(预选拆件驱动)——拍板后立帧数规格与资产工单。 - 相关文档:[art-bible](design/art/art-bible.md) §8.4 帧数规格 · 战斗表现批 @@ -192,13 +192,13 @@ #### 棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计(2026-10-30 登记,来源=会话提及) -**实况**:用户口径=封印图标现为扫雷旗形,改法阵或锁链待设计,形未定先做讨论。现件 `game/assets/art/board/seal_mark.png`(32×32 旗形剪影);消费面两处——棋盘封印槽绘制(`tile_palette.gd:19` → `board_view.gd:616`)与战末金蜡封徽记(`battle_end_overlay.gd:65-69` 复用同图);「旗」概念已全库退役(2026-10-18),文档口径仍以旗为形待换代:[美术圣经](design/art/art-bible.md) v3.23 §3.2/§4.1、[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4、[战斗屏 UX](design/gdd/ux/battle.md) :128/:270、[体验验证清单](体验验证清单.md) 第 4 条。 +**实况**:2026-11-04 讨论定案——形=**环+十字法阵**(外环=圈定,环内十字=钉住此格,四端顶到环内沿连成一体);色=封印黑 `#000000` 纯黑无光效(封印紫在封印黑批已退役、不并存);画法=纯黑实心块面、禁细线(32px 下细线断成碎点)。尺寸定值落 [美术圣经](design/art/art-bible.md) v3.38 §8.2「封印印记源档」(外径 23px/环壁 2.6px/十字臂 3.2px/外沿距格边 ≥4px),形与画法落 §3.2。终稿已同名替换 `game/assets/art/board/seal_mark.png`(32×32)。连带件=战末金蜡封徽记沿用同图(零代码改动);特效两件只做常显。消费面 `tile_palette.gd:19`、`board_view.gd:539`/`:582`、`battle_end_overlay.gd:84`。同批清圣经四处「旗」与 §4.1 旧形前提,[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 同步;圣经 :370/:540 两处「背包旗」属 character-design 命名,未动。 -**缺口**:还差封印印记图形设计讨论(法阵/锁链两案并列,禁代裁),须一并裁五点:①形(与危险格巢穴纹、奖励徽章剪影语言 art-bible §3.1 互辨);②色(法阵带发光/色相与 §4.1 封印黑不变槽冲突,须重登记判据);③尺寸遮挡(32×32 内「数字 > 小人 > 封印印记」栈位不侵入数字字形区);④金蜡封徽记与悬停「封印(印记形)」光标是否同步换;⑤与封印特效两件(施印瞬间/常显形态)合并或分立。裁决写入 art-bible §3.2/§4.1 与两份美术清单后方可出样张(终稿名沿用 `seal_mark.png`)。 +**缺口**:还差两件:①实机目检(三关封印印记形状可读、与巢穴纹/变异纹带不同格混淆、战末蜡封观感);②施印瞬间特效未实装(清单 §4 两件中的第一件)。 **改动面**:文档+美术素材 -**状态**:待美术设计 +**状态**:美术设计中 ### 教学引导 From 5fc9a131bb4001486643ff209b8d6d44aee9a69b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:20:03 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E5=86=99=E5=8D=A1=E4=BF=AE=E8=AE=A2(?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=AE=A1=E6=9F=A5presentation=E5=B1=82?= =?UTF-8?q?=E5=B0=81=E8=A3=85=E4=BF=AE=E5=A4=8D=E6=89=B9v1.1):=20grilling?= =?UTF-8?q?=E5=A4=8D=E6=A0=B8=E5=85=AD=E9=A1=B9=E8=87=AA=E8=A3=81=E6=8A=98?= =?UTF-8?q?=E5=85=A5=E2=80=94=E2=80=94T2/T5=E6=97=B6=E5=BA=8F=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3(:69=E4=B8=8Eui=5Fkit=E5=85=AC=E5=BC=80=E5=8C=96?= =?UTF-8?q?=E5=90=8C=E7=A7=BBT5)+main.gd=E4=BE=8B=E5=A4=96=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E5=AE=9E=E6=95=B0=E5=8C=96(25-30=E2=86=9228-32=3D?= =?UTF-8?q?=E6=97=A2=E6=9C=891+=E6=9C=8D=E5=8A=A117+=E5=8F=A5=E6=9F=84?= =?UTF-8?q?=E5=8F=A312)+flash=E5=BD=92=E4=BD=8D=E5=BD=A2=E6=80=81=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3(vitals=E8=87=AA=E6=8C=81owner=E4=B8=8Etween=E5=85=88?= =?UTF-8?q?=E4=BE=8B=5Fplay=5Fhp=5Fflash,=E5=AE=BF=E4=B8=BB=E6=88=90?= =?UTF-8?q?=E5=91=98=E9=9B=B6=E5=A2=9E)+T3=E7=AD=BE=E5=90=8D=E5=AE=9A?= =?UTF-8?q?=E7=A8=BF(vitals14/motion14/hover7,=E7=BB=98=E5=88=B6=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E9=80=90=E4=B8=AAgetter=E5=85=8D=E5=B8=A7=E5=88=86?= =?UTF-8?q?=E9=85=8D)+=E5=89=8A=E5=BC=B1=E7=94=B3=E6=8A=A5=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0ui=5Fstruct=5Fprobe=E4=B8=A4=E4=B8=B2;=20=E5=A4=84?= =?UTF-8?q?=E7=BD=AE=E8=AE=B0=E5=BD=95=E5=90=8C=E6=AD=A53=E5=A4=84(T1?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E4=B9=89=E5=8A=A1);=20=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E4=B8=89=E6=9F=A5NEVER/Gotchas/=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E9=A2=84=E7=AE=97=E9=9B=B6=E5=91=BD=E4=B8=AD=E6=9C=AA=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E9=A2=86=E5=9F=9Freference(=E9=80=90=E5=AD=97?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=97=A0=E6=96=B0=E5=BD=A2=E6=80=81);=20pipe?= =?UTF-8?q?line=5Fgate=20PASS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../处置-presentation层封装修复-2026-11-04.md | 6 ++--- ...架构审查presentation层封装修复批-2026-11-04.md | 24 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/reports/处置-presentation层封装修复-2026-11-04.md b/reports/处置-presentation层封装修复-2026-11-04.md index d3eba28b..ee92ef6b 100644 --- a/reports/处置-presentation层封装修复-2026-11-04.md +++ b/reports/处置-presentation层封装修复-2026-11-04.md @@ -57,7 +57,7 @@ | _order | :131 写;:410 读 | 同上 | | _dead | :173/:262 写(击杀/复活);:91/:98/:138/:171/:248/:277 读 | 生命周期行为——归位为子件公开方法(标记死亡/复位),宿主停写 | | _hp_cur | :419 读 | 子件自管(宿主零直写)——读走公开读法 | -| _hp_flash_tw | :254-258 宿主自建 tween 改宿主自身 modulate | 误放子件的宿主动画句柄——**归位回 standee_body 成员**(成员 10→11,预算内) | +| _hp_flash_tw | :254-258 宿主自建 tween 改宿主自身 modulate | 归位为子件行为方法 `play_hp_flash()`——vitals 已自持 `_owner: Node2D` 与 tween 先例(apply_hp/_hp_tween 同型),句柄留 vitals,宿主成员零增 | **standee_body.gd → mo(standee_motion.gd 实例,7 字段)**:_hero_state :40 写(生成时置 IDLE)→ 走既有公开方法 apply_hero_state(:300 在役先例);_intent_visible/_glyph/_col/_scale/_rot 写 :140/:145/:159-161/:181、读 :58/:431-458 → 意图显示是演出行为——归位为子件公开方法(显示/隐藏/复位),绘制所需状态经一个公开读取口交付(宿主 _draw 消费,:439-458 现役);_portrait_sprite :284/:431 读、生成期挂载 → 挂载走公开口,绘制读取走公开读法。 @@ -87,10 +87,10 @@ | 触件 | 公开方法现值 | 成员现值 | 本批预期 | |---|---|---|---| -| main.gd | 1 | 15(默认顶) | 公开方法升 25–30——**指标例外申报**(规则 B6);成员零增 | +| main.gd | 1 | 15(默认顶) | 公开方法升 28–32(构成=既有 1+服务 17+句柄口 12)——**指标例外申报**(规则 B6);成员零增 | | 五分文件 | 各 0 | 各 0 | 公开方法 3–15,预算内 | | level_map_screen / battle_end_overlay / debug_overlay | 8/6/19 | 7/5/4 | A1 只改字段名,两键零变化 | -| standee_body | 20(顶) | 10 | 公开方法零增;成员 10→11(_hp_flash_tw 归位),预算内 | +| standee_body | 20(顶) | 10 | 公开方法与成员双零增(flash 行为归位 vitals 自持) | | standee_vitals / standee_motion | 1/6 | 12/11 | 行为归位+读投影公开方法净增(各 ≤20 内);成员零增 | | board_view | 27(基线在册) | 31(基线在册) | **零净增**(C 族走构造交付;A2 走 board_hover 公开面) | | board_hover | 0 | 9 | 公开方法 +6,预算内 | diff --git a/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md b/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md index 86fe9377..fe6997d0 100644 --- a/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md +++ b/reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md @@ -1,6 +1,6 @@ # 需求卡片:架构审查 presentation 层封装修复批(2026-11-04) -来源:待办清单「架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移」条目 presentation 层私取面部分(**唯一事实源**:reports/处置-presentation层封装修复-2026-11-04.md——处置记录与本卡并行成文,本卡开工时 T1 首步核其已落盘且与本卡第 3 节逐条一致,冲突即 L3 停卡。设计闭合凭据=2026-11-04 设计讨论会话用户逐项裁决〔改法A=三个内部数据类字段公开化;方案一=四族一批做完〕+审计报告 presentation 普查+code-conventions §1.6)。验收档:**中**(零行为变更批——做错的后果玩家可见但不损持久状态:存档/RNG/结算/数值零触碰)。改动面:游戏代码(含随逻辑改的测试代码)。 +来源:待办清单「架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移」条目 presentation 层私取面部分(**唯一事实源**:reports/处置-presentation层封装修复-2026-11-04.md——处置记录与本卡并行成文,本卡开工时 T1 首步核其已落盘且与本卡第 3 节逐条一致,冲突即 L3 停卡。设计闭合凭据=2026-11-04 设计讨论会话用户逐项裁决〔改法A=三个内部数据类字段公开化;方案一=四族一批做完〕+审计报告 presentation 普查+code-conventions §1.6)。验收档:**中**(零行为变更批——做错的后果玩家可见但不损持久状态:存档/RNG/结算/数值零触碰)。状态:已写卡(v1.1,2026-11-04 grilling 复核折入——T2/T5 时序修正+main.gd 例外区间实数化+flash 归位形态修正+T3 签名定稿+削弱申报追加)。改动面:游戏代码(含随逻辑改的测试代码)。 ## 1. 目标与完成定义 @@ -86,7 +86,7 @@ A1 三件 A2 五件 game/presentation/views/standee_vitals.gd 【非行为 R2】构造/配置交付口+死亡/复位行为方法+读投影(成员 12 零增) game/presentation/views/standee_motion.gd 【非行为 R2】意图显示/隐藏/复位行为方法+绘制状态读口+挂载口(成员 11 零增) - game/presentation/views/standee_body.gd 【非行为 R2】55 处改址+_hp_flash_tw 归位为宿主成员(10→11)+头注 + game/presentation/views/standee_body.gd 【非行为 R2】55 处改址+头注(成员零增——flash 行为归位 vitals 自持 tween) game/presentation/views/board_hover.gd 【非行为 R2】+6 公开面(重绑口/复位/刷新/悬停/chord/连接目标/读口) game/presentation/views/board_view.gd 【非行为 R2】7 处改址(公开方法与成员零净增——贴顶件) B 七件 @@ -120,7 +120,7 @@ C 四件 **T2 A1 三类字段公开化**(估时 0.5 工作日;区间 0.25–0.75) 取用:T1 底账。产出:A1 族 206 行清零。 -- [ ] level_map_screen.gd:处置记录 §三 A1 表 14 字段去下划线(体零改动),87 处 `_map._x` → `_map.x`;:69 `UiKit._gradient_base` → `UiKit.gradient_base`(随 T5 公开化先行改址,孤立验证在 T5 对账);头注补一句内部数据类定位 +- [ ] level_map_screen.gd:处置记录 §三 A1 表 14 字段去下划线(体零改动),87 处 `_map._x` → `_map.x`;头注补一句内部数据类定位(:69 UiKit 调用改址随 T5 同批生效) - [ ] battle_end_overlay.gd:同型 14 字段+69 处;debug_overlay.gd:同型 14 字段+50 处 - [ ] sweep 测试件骨架与 A1 断言组先行落盘(presentation_encapsulation_sweep_test.gd:三文件非空+class 正锚+`_map\._|st\._` 模式零命中+公开字段正锚+`_MapState|_EndOverlayState|_DebugState` 文件外零消费者断言〔pres封装 V1〕) - [ ] 孤立验证:sweep A1 组+冒烟——出红只跑红件最小单元 @@ -128,9 +128,9 @@ C 四件 **T3 A2 standee 与 board_hover**(估时 1.0 工作日;区间 0.75–1.5) 取用:T1 底账、处置记录 §三 A2 逐字段归属表。产出:A2 族 62 行清零。 -- [ ] standee_vitals.gd:身份/配置字段(kind/display_name/enemy_id/armor/order/hp_max)收构造与配置交付口(签名按现役调用点数据形态定,成员 12 零增);_dead 归位为 `mark_dead()`/`reset()` 型公开方法;读投影(display_name()/hp_cur()/is_dead() 型)按宿主消费面落 -- [ ] standee_motion.gd:意图行为归位 `show_intent(glyph, col)`/`hide_intent()`/`reset()` 型;绘制状态读口一个(返回 intent 绘制四态,宿主 _draw 消费);portrait 挂载口+读口;_hero_state 生成期走既有 apply_hero_state -- [ ] standee_body.gd:55 处改址(逐字段按归属表);`var _hp_flash_tw` 归位宿主成员(:254-258 逻辑随迁,体逐字);头注「外部直访冻结成员」句按新面改写 +- [ ] standee_vitals.gd 公开面 14(既有 apply_hp+新增 13,成员 12 零增):`configure_identity(kind: String, display_name: String, enemy_id: String, armor: String)`、`set_max_hp(v: float)`、`set_order(i: int)`、`set_dead(v: bool)`、读投影 `is_dead()/kind()/armor()/display_name()/enemy_id()/order()/hp_cur()/hp_max()`——宿主 :39/:82-85/:96/:131/:173/:262 写点与 :48/:53/:66-69/:91/:98/:138/:171/:246-248/:277/:340-342/:419/:465-482 读点逐一对口 +- [ ] standee_motion.gd 公开面 14(既有 6+新增 8,成员 11 零增):`show_intent(glyph: String, col: Color)`、`hide_intent()`(:140/:145/:181 三处同型归位);绘制路径读投影逐个 getter `intent_visible()/intent_glyph()/intent_col()/intent_scale()/intent_rot()/portrait_sprite()`(_draw 每帧调用,免逐帧字典分配——性能预算依据);_hero_state 生成期走既有 apply_hero_state;非绘制路径沿用现役 state_probe() 字典形态 +- [ ] standee_body.gd:55 处改址(逐字段按归属表;flash_hit() 体改调 `st.play_hp_flash()`,tween 逻辑整体归位 vitals——宿主成员零增);头注「外部直访冻结成员」句按新面改写 - [ ] board_hover.gd:+6 公开面(rebind(minesweeper)/reset()/refresh_after_paint()/hover_at(pos)/detect_chord(c)/on_shell_mouse_exited 更名公开化/hover_pos() 读口——七处消费逐一对口);board_view.gd 7 处改址 - [ ] sweep A2 断言组(standee_body `\bst\._|\bmo\._` 零命中+board_view `_hover_comp\._` 零命中+新公开面正锚〔pres封装 V2〕) - [ ] 孤立验证:sweep A2 组+渲染 golden+战斗探针——出红只跑红件最小单元 @@ -150,7 +150,7 @@ C 四件 **T5 C 族+批收口**(估时 0.375 工作日;区间 0.25–0.5) 取用:全部前卡。产出:C 族清零+结构性恒零证据+批收口两件。 -- [ ] ui_kit.gd 三工厂公开化(体逐字);cell_mark_overlay 构造收遮罩下发口(board_view 构造点随改,board_view 面零净增)+2 处改址;ui_assembler/settings_overlay 改址;level_map_screen :69 改址对账(T2 已预改) +- [ ] ui_kit.gd 三工厂公开化(体逐字);cell_mark_overlay 构造收遮罩下发口(board_view 构造点随改,board_view 面零净增)+2 处改址;ui_assembler/settings_overlay 改址;level_map_screen :69 `UiKit.gradient_base` 改址(与本步 ui_kit 公开化同批生效) - [ ] sweep C 断言组(ui_kit 私有工厂全库零外调+cell_mark `_owner\._` 零命中+正锚〔pres封装 V4〕);run_all.gd manifest 追加 1 行(sweep,expect 0) - [ ] 全量 sweep 复核+冒烟+渲染 golden+探针套件终读数(零行为变更证据链闭合) - [ ] **实现报告** `reports/iter-实现报告-<日期>.md`:各卡状态+S13 键改前改后对账(T1 基线逐件核+main.gd 例外键落基线记录)+探针替换清单执行账+实现决定记录 @@ -169,11 +169,11 @@ T1 0.25|T2 0.5|T3 1.0|T4 2.0|T5 0.375。区间合计 3.5–5.25 工作 |---|---|---|---|---|---|---| | R1 A1·战末弹层 | battle_end_overlay 14 字段+69 处改址 | sweep A1 组〔pres封装 V1〕;冒烟+战末弹层既有探针回归 | 探针 | `BattleEndOverlay.open_victory()`(战末弹层真实开合链) | 私取模式零命中+公开字段正锚+文件外零消费者;弹层可见性与载荷零漂移 | 既有:站桩敌人呈现与结算演出落点 | | R2 A1·调试浮窗 | debug_overlay 14 字段+50 处改址 | sweep A1 组〔pres封装 V1〕;调参相关探针既有回归 | 探针 | `TuneSimController.run()`(调试浮窗批量模拟入口) | 同 R1 判据面;浮窗呈现与批量模拟读数零漂移 | 既有:调参测速批量模拟(逐帧步进与统计读数) | -| R3 A1·关卡画面 | level_map_screen 14 字段+87 处+:69 改址 | sweep A1 组〔pres封装 V1〕;冒烟+选路集成既有回归 | 集成+探针 | 选路集成链(起关→选路→断点重生成)+冒烟全链 | 私取模式零命中+公开字段正锚;选路链零漂移 | 既有:起关/选路/断点重生成同图(集成) | +| R3 A1·关卡画面 | level_map_screen 14 字段+87 处改址 | sweep A1 组〔pres封装 V1〕;冒烟+选路集成既有回归 | 集成+探针 | 选路集成链(起关→选路→断点重生成)+冒烟全链 | 私取模式零命中+公开字段正锚;选路链零漂移 | 既有:起关/选路/断点重生成同图(集成) | | R4 A2·站桩三件 | standee 三件改址+_hp_flash_tw 归位 | sweep A2 组〔pres封装 V2〕;渲染 golden 逐像素零转红;站桩探针既有回归 | 探针+golden | `BattleStageView` 真实实例化(站桩渲染链) | 站桩呈现逐位同;子件私取模式零命中+新公开面正锚 | 既有:站桩敌人呈现与结算演出落点 | | R5 A2·悬停子件 | board_hover 六公开面+board_view 7 处改址 | sweep A2 组〔pres封装 V2〕;ui_input_probe 既有回归 | 探针 | `ui_input_probe` 合成鼠标事件→`BoardView._gui_input()` | 悬停三语义零漂移;子件私取零命中+六公开面正锚 | 既有:棋盘悬停反馈(三语义光标/目标格边框/路径预览点迹) | | R6 B·组合根交接面 | main+五分文件+探针/测试替换 | sweep B 组〔pres封装 V3〕;14 件替换后当轮真实跑绿;冒烟+系统测试 | 集成+探针 | `ui_click_probe` 大门两分支段(main 处理器→分文件→营地态全链)+探针套件其余件 | 六文件私取模式零命中+新公开面正锚;探针断言零删改引用改址后全绿 | 既有:营地大门两分支确认与断点续玩接线 | -| R7 C·杂项公开面 | ui_kit 三工厂+cell_mark 构造交付+改址 | sweep C 组〔pres封装 V4〕;layout/shot 探针既有回归 | 单元+探针 | 界面装配真实链(ui_kit 工厂消费面) | 私有工厂零外调+cell_mark 私取零命中+呈现零漂移 | 既有:站桩呈现定值与素材绑定 | +| R7 C·杂项公开面 | ui_kit 三工厂+level_map :69+cell_mark 构造交付+改址 | sweep C 组〔pres封装 V4〕;layout/shot 探针既有回归 | 单元+探针 | 界面装配真实链(ui_kit 工厂消费面) | 私有工厂零外调+cell_mark 私取零命中+呈现零漂移 | 既有:站桩呈现定值与素材绑定 | **零行为改动面的证据义务**:本卡全部行为面为非行为改动(零行为变更批)——不设「行为改动」行;每行零变更证明=渲染 golden 零转红+探针套件零转红+冒烟/单元零转红+sweep 恒零。能力行全部「既有」、零新增——判断依据:玩家可观测能力面零变化,无新能力可登记,`game/tests/README.md` 零编辑(批末核对义务在 T5)。 @@ -182,6 +182,8 @@ T1 0.25|T2 0.5|T3 1.0|T4 2.0|T5 0.375。区间合计 3.5–5.25 工作 **测试削弱申报**(允许改/删的旧断言全清单;未申报的删改由独立验收审计判红): 1. 探针/测试 14 件中与公开化名字交集的 `main._x` 引用 → 改公开名(清单=T1 精确清单)——随被测改动同步的引用形态迁移,断言内容零改动,无一条删除断言或放宽阈值。 +**测试削弱申报补充(v1.1)**:2. ui_struct_probe.gd:144-146 退出调用链源码串断言两处("MainEscFlow._on_quit(self)"→"MainEscFlow.on_quit(self)"、"root._quit_game()"→"root.quit_game()")——随公开化改址的引用形态迁移,断言意图(调用链完整)不变;同函数 :141 `get_method() == "_on_esc_quit"` 断言零改动(处理器保私有)。 + **独立验收审计**:入口=批末 T5 后、合并前;报告=`reports/iter-验收审计-<日期>.md`;由没参与实现的会话或子代理执行,核对:对照表逐行断言真实存在且当轮跑批通过、零行为变更证据链在案(golden/探针/冒烟改前改后零转红读数)、测试削弱申报 1 条之外零删改、第 13 节无效成员零残留、S13 键对账(含 main.gd 例外键申报一致)。未过不合并。 ## 9. 异常分级 @@ -226,8 +228,8 @@ T1 0.25|T2 0.5|T3 1.0|T4 2.0|T5 0.375。区间合计 3.5–5.25 工作 新增 .gd 仅 1 个测试件(SceneTree 测试脚本,全类型标注、零公开生产面)。生产代码全落既有件,按「存量触面一查」对账(S13 默认阈值:公开方法 ≤20/类变量 ≤15/分支 ≤8/返回 ≤6): -- **指标例外申报(main.gd)**:键=`game/presentation/main.gd|quality/max-public-methods`,现值 1,预期 25–30(T1 实数核);理由=组合根服务面(分文件消费的公开口集中于此),架构先例=level_runner.gd 公开方法 33 在册基线、adventure_runner.gd 类变量 23 在册基线;落法=实现批 `python tools/gdstyle_gate.py --refresh`(人工专用通道,随批重生成)。 -- **贴顶件约束**:board_view 27/31(基线在册)**零净增**(本批对 board_view 的全部动作=7 处改址+构造点传参);standee_body 公开方法 20 贴顶零增(成员 10→11 仅 _hp_flash_tw 归位);debug_overlay 公开方法 19——A1 只改字段名不动方法,零变化。 +- **指标例外申报(main.gd)**:键=`game/presentation/main.gd|quality/max-public-methods`,现值 1,预期 28–32(构成=既有 1+服务公开化 17+句柄读写口 12;T1 实数核);理由=组合根服务面(分文件消费的公开口集中于此),架构先例=level_runner.gd 公开方法 33 在册基线、adventure_runner.gd 类变量 23 在册基线;落法=实现批 `python tools/gdstyle_gate.py --refresh`(人工专用通道,随批重生成)。 +- **贴顶件约束**:board_view 27/31(基线在册)**零净增**(本批对 board_view 的全部动作=7 处改址+构造点传参);standee_body 公开方法 20 贴顶零增(成员零增——flash 归位 vitals 自持);debug_overlay 公开方法 19——A1 只改字段名不动方法,零变化。 - **类变量净增件**:cell_mark_overlay 13→14(构造交付的遮罩口引用);其余触件零增(A1 改名不增数;vitals/motion 成员零增)。 - **公开方法净增件**:五分文件 0→3–15;board_hover 0→6;standee_vitals 1→预算内、standee_motion 6→预算内(均 ≤20);ui_kit 0→3;main.gd 见例外申报。 - **迁移函数对账**:本批公开化方法全部「体逐字去下划线」,无跨文件迁移体;唯一迁移=_hp_flash_tw 归位(standee_body 内自用逻辑随迁,非跨件函数迁移);若 T1 读数发现源函数 branches/returns 超默认登记值,随迁即拆函数(拆分不改语义,与第 13 节区分)。 From 95727086121fd935f97a2d9714505217cfcbe9c4 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:23:35 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E6=96=87=E6=A1=A3(=E8=BD=A6=E9=81=93?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E5=8D=95=E5=8C=BA=E8=90=BD=E6=96=87=C2=B7?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=A3=81):=20process-conventions=20=C2=A71?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=E7=AC=AC5=E6=9D=A1=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=8D=95=E5=8C=BA(=E5=BB=BA=E5=8C=BA=E7=B2=92=E5=BA=A6=3D?= =?UTF-8?q?=E5=9B=9E=E5=90=88/=E5=90=8C=E4=BC=9A=E8=AF=9D=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E4=BA=8B=E9=A1=B9=E5=B9=B6=E5=85=A5=E5=9C=A8=E5=BD=B9?= =?UTF-8?q?=E5=8C=BA/=E5=8C=BA=E5=86=85=E4=BA=8B=E9=A1=B9=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=8F=AA=E5=81=9A=E4=B8=80=E6=AC=A1=E5=90=88=E5=85=A5?= =?UTF-8?q?=E4=B8=80=E6=AC=A1CI/=E5=90=88=E5=9B=9E=E5=90=8E=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=86=8D=E5=8F=91=E4=BB=A4=3D=E6=96=B0=E5=9B=9E?= =?UTF-8?q?=E5=90=88=E5=8F=A6=E5=BC=80=E5=8C=BA/fix=E5=8C=BA=E7=AE=97?= =?UTF-8?q?=E8=BF=BD=E7=BB=BF=E5=BB=B6=E7=BB=AD/=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=89=B9=E7=8B=AC=E5=8D=A0=E5=9B=9E=E5=90=88?= =?UTF-8?q?)=EF=BC=8B=E7=AC=AC1=E6=9D=A1=E4=B8=8E=C2=A73=E7=AC=AC4?= =?UTF-8?q?=E6=9D=A1=E5=BB=BA=E5=8C=BA=E7=B2=92=E5=BA=A6=E5=8F=A5=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=94=B9=E5=9B=9E=E5=90=88=E5=8F=A3=E5=BE=84;=20AGENT?= =?UTF-8?q?S.md=20=E8=BD=A6=E9=81=93=E7=BA=A2=E7=BA=BF=E5=8F=A5=E5=90=8C?= =?UTF-8?q?=E6=AD=A5;=20--docs=20=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E9=9B=B6=E6=96=B0=E5=A2=9E=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- design/engineering/process-conventions.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 47711831..c5deaf5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,7 +103,7 @@ ## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约) -- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后远端 CI(post-merge 钩子推送触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1) +- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;会话单区(2026-11-04 用户裁)——同一时刻每会话至多一个 worktree 在役,同会话追加事项并入在役区、只做一次合入(一次 CI),合回后用户再发令=新回合另开区,迭代实现批独占回合(细则见 process-conventions §1 第 5 条);主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后远端 CI(post-merge 钩子推送触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1) - **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节) - 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测 - 合回后验证 = 远端 CI 闸全绿(唯一强制验证点;唯一入口、唯一闸清单、唯一域划分与选闸算法仍只在 `tools/run_all_gates.sh`,禁在文档另列;触发与追绿语义见「运行与验证」首条与 process-conventions §1)。开发期手动跑批按需选档(`--code` 关联档选测——02 跑「卡片申报 ∪ diff 推导」并集套件、三件保险恒跑、横切面回落全量——算法只在闸脚本内定义),手动跑不构成验收;全量回归由用户自发发起。远端按改动域分流:文档面批走文档域秒级闸,代码面批走两域全量(分流算法单点=`tools/ci/domain_route.sh`) diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index 09307339..157911e1 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -9,10 +9,11 @@ 主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;2026-10-30 的「主区直改+直提」车道与 commit 点 pre-commit 直改闸随之退役;验证模型同构——合回后远端 CI 是唯一强制验证点,2026-11-03 用户裁,细则见本节第 3 条): -1. **worktree 车道(一切面)**:每批先建独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) +1. **worktree 车道(一切面)**:每回合先建独立 worktree(建区粒度=回合,会话单区细则见第 5 条):`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) 2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑 3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。回执由 `tools/ci_watch.sh remote` 轮询 Gitea API 写回 `game/tests/.gates/ci-status.json`(合同不变:commit/verdict/red_gates/key_lines/note),agent 开工先读 CI 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**回执等待纪律(2026-11-02 用户裁)**:合回落地即向用户呈报本批总结,回执等待一律以 run_in_background 后台任务运行 `tools/ci_watch.sh remote <合并提交>`(认领判据与容错细则=该件头注),禁前台 sleep/轮询循环等 CI;后台任务唤醒会话后按回执续行——绿即删区收口,红或超时即呈报走修复批。闸重跑等一切分钟级跑批等待同此纪律 4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(推送+挂回执等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) +5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。 ## 2. 五段流水线 @@ -40,7 +41,7 @@ 3. **用户发开工令才动手**——需求卡片经用户批准(U0)后方开工 **环境与基线** -4. **一切批独立 worktree+拷导入缓存**——按 §1 建 worktree;涉代码域跑闸或测试的批先拷主区 `.godot/` 目录与字体 `.import` 边车(纯文档批免拷),否则全量重导入资源近似卡死。检出工作区零测试,直接开工(2026-10-22 频率规则);闸壳每次跑批先自检/自愈本区 user 目录隔离(§1 第 1 条),建不起来即拒跑 +4. **一切批走 worktree 车道+拷导入缓存**——按 §1 建 worktree(建区粒度=回合,同回合多批共用一区——§1 第 5 条);涉代码域跑闸或测试的批先拷主区 `.godot/` 目录与字体 `.import` 边车(纯文档批免拷),否则全量重导入资源近似卡死。检出工作区零测试,直接开工(2026-10-22 频率规则);闸壳每次跑批先自检/自愈本区 user 目录隔离(§1 第 1 条),建不起来即拒跑 5. **涉数值改动先采改前读数**——与改后同随机种子配对比较并写明逐项比对口径;无数值改动禁跑模拟。**Alpha 阶段口径(2026-11-04 用户裁)**:Alpha 阶段先做机制与内容填充,数值层统一修正殿后——本阶段的投放批与内容批零读数、零 MC 取证,改前读数与难度带读数义务后移到数值统一修正批执行;本条对数值统一修正批本身恢复原义。 **实施** From 3fd788a31f3a42936709b2252e63427988677455 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:41:11 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E6=96=87=E6=A1=A3(=E5=B0=81=E5=8D=B0?= =?UTF-8?q?=E5=8D=B0=E8=AE=B0=C2=B7=E7=BE=8E=E6=9C=AF=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E9=97=AD=E5=90=88):=20=E7=94=BB=E6=B3=95=E6=94=B9=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=8C=96=E7=BB=98=E5=88=B6=E3=80=81=E5=AE=9A=E5=80=BC?= =?UTF-8?q?=E5=85=A5=20=C2=A78.2=20=E5=8F=82=E6=95=B0=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - art-bible v3.41: §3.2 封印印记条改写——画法=着色器按格边实时画环+十字(贴图在 26–46px 格边必非整数缩放)+环外一圈压暗光(纯黑只加 alpha、到格边为止不外溢)+层序纪律(低于小人、迷雾态隐藏); §8.2 新增「封印印记参数表」(环外径 82% 格宽/壁厚 11% 环径/臂宽 15.6% 环径/光圈宽 9% 格边/峰值 α0.50/衰减 1.6/参数单点=ui_kit 四常量); §8.2 甲类整数倍条移除封印印记并改指参数表 - 两份美术清单同步: §3.3 补参数化说明+§4 封印特效行改「常显=参数化绘制件」; art-status P2/P3 两行改写 - 待办清单本条: 状态 美术设计中→美术设计已闭合(凭据=art-bible v3.41 §3.2/§8.2+2026-11-04 用户点头 82% 档), 删缺口, 改动面补「游戏代码」, 实况收敛为定案参数与待退役贴图路径 - pipeline_gate PASS(28 条目/51 总表行/3 链) --- design/art/art-assets-checklist.md | 4 ++-- design/art/art-bible.md | 8 ++++---- design/art/art-status-checklist.md | 4 ++-- 待办清单.md | 12 +++--------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/design/art/art-assets-checklist.md b/design/art/art-assets-checklist.md index 1924aad5..fc17a9de 100644 --- a/design/art/art-assets-checklist.md +++ b/design/art/art-assets-checklist.md @@ -117,7 +117,7 @@ ### 3.3 雷 / 封印印记 / 数字格面像素化 ⚙ -- **封印印记已换代**:形 = 环+十字法阵、纯黑 32×32,形与定值见 art-bible §3.2 与 §8.2「封印印记源档」;本节余下面 = 雷格纹样与数字格面。 +- **封印印记改为参数化绘制**:形 = 环+十字法阵,由着色器按格边实时画(环径 82% 格宽+环外一圈压暗光),形与定值见 art-bible §3.2 与 §8.2「封印印记参数表」;贴图件仅战末金蜡封徽记继续使用。本节余下面 = 雷格纹样与数字格面。 - **要什么**:格子样张通过后按冻结参数出全套砖面资产(含图类型换色板组)。 - **来源**:⚙ 脚本管线自产——未开/已开/危险格/变异纹带是扫雷专属语义(角嵌纹样已不存在),素材站没有对的;砖面**纹理底噪**可 ♻ 用 CC0 无缝石/土纹理作脚本管线的纹理输入,量化到色板(混合路线)。 @@ -164,7 +164,7 @@ | 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint;连续地面层在其下方,先保留 | ⚙/✦ 自产 | | 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | ✦/⚙ 自产(同 §2.3) | | 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | ♻ 粒子/碎块改色 + ⚙ 坍塌结构自产 | -| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记常显形态**已落位**——`seal_mark.png`(32×32 纯黑,环+十字法阵;形与定值见 art-bible §3.2/§8.2);施印瞬间特效**未实装**,仍缺(走运动+色相,明度分层不动) | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | +| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记常显形态=**参数化绘制件**(着色器画环+十字+环外压暗光,形与参数见 art-bible §3.2/§8.2);施印瞬间特效**未实装**,仍缺(走运动+色相,明度分层不动) | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) | | 弹道/退场等动画密度 | 战斗表现输入清单,方案未定 | 样张锁动画方案后启动 | ♻ 通用弹道/光点/消散粒子改色为主 | **现状**:三关舞台背景层与连续地面层已交付(2026-10-31 入包 `game/assets/art/scene/`:dungeon v10c / underdark v10c / deep_underdark v10c,均 1280×252;共用地面 v12b,1280×100,按作者裁保留原亮度 L≈0.132);小石台件已在役。 diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 0e1ba0e4..4d6221a2 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -1,6 +1,6 @@ # 美术圣经(Art Bible)— Dungeon Sweeper -> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.40,历次修订明细见 git 提交史 +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.41,历次修订明细见 git 提交史 > > **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。 > 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。 @@ -164,7 +164,7 @@ - **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三 - **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3 - **未开格预读通道(微光单通道)**:类型预读 = 类型微光单通道(色相+明度双码,色带 §4.2);色弱类型主通道 = 微光明度梯度(组粒度——组间互辨;§2 微光明度例外);格内零角嵌/箭头/加号类附加形(微光格只留微光)。验收:目检〈微光格内零角嵌/箭头/加号类附加形〉 ←原一;原三 -- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法纪律**:纯黑实心剪影,禁细线描边、禁符文字形——32px 下细线会断成碎点,阵纹一律做成实心块面。**尺寸**见 §8.2「封印印记源档」条(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),无发光、无描边、无渐变。**占位**:印记居格心,最外沿不越格缘空带(见 §8.2);封印印记只出现在未开格,不与数字同格。格内栈位见 §1.2。封印特效(施印瞬间 + 印记常显形态)资产归清单 §4 演出类——常显形态即本件,施印瞬间特效未实装。封印印记纹理已落位(现行件 = `seal_mark.png`)。验收:目检〈印记为环+十字、四端与环相接;无发光与描边;最外沿不越格缘空带〉 ←原一;P2 +- **封印印记**:封印动作的叙事呈现 = **封印**(玩家可见专名,术语定义见 [术语表](../gdd/术语表.md)「封印」条;动作面见《排雷玩法》)——封印动作落在格上的印记称封印印记。**形 = 法阵**:一圈外环+环内一个十字,十字四端顶到环内沿、与环连成一体——两层构成取法阵通用骨架(外环=圈定范围,环内图形=力量图形,非指特定作品):环=封印圈定,十字=钉住此格。**画法 = 参数化绘制**(着色器按格边实时画环与十字,不出贴图):格边 26–46px 连续可变,任何贴图源都得非整数缩放(32px 源在 28 格缩到 0.875、在 46 格放到 1.44),像素必宽窄不均;参数化按比例画,任意格边清晰。**环外光圈**:印记外面一圈**压暗光**(纯黑只加透明度)——自环外缘起、向外衰减到格边为止、**不外溢邻格**,作用 =「此格被罩住」;禁中心亮、禁色相、禁呼吸。**尺寸定值**见 §8.2「封印印记参数表」(本处不复述数值)。**色** = 封印黑 `#000000`(§4.1),光圈只改 alpha、不新增色。**占位**:印记居格心,环外留 9% 格宽给光圈(见 §8.2);封印印记只出现在未开格,不与数字同格。格内栈位见 §1.2;层序纪律——印记层低于小人,且迷雾态(区外全黑/记忆态)隐藏(§6.5「封印随格隐没」)。封印特效两件:常显形态即本件,施印瞬间未实装。战末金蜡封徽记仍取贴图 `seal_mark.png`(与棋盘侧参数化绘制并存,两处互不依赖)。验收:目检〈环径占格宽 82%±1px;光圈只在环外且不出格;纯黑无色相〉 ←原一;P2 - **可挖域接触带微光(交互反馈层)**:挖掘期,英雄格周界与可挖域(概念见《排雷玩法》动作合法性节;呈现义务见《HUD框架》可挖域指示)的接触面画呼吸微光——正交邻墙 = 该侧整条边,斜向邻墙 = 共享角点 + 向两侧各延伸半条边;光只画英雄格(空间)一侧、不进墙格(与装饰变体零冲突,数字砖心不涉);微光 = 空间的灯光边界(画法规则)。色相 = 蓝族浅一档(§4.2 蓝条;与主角蓝框的分层由样张测试定,§1.4);呼吸 = §2.1 挖掘期唯一在动元素,减动效停格。角点为斜墙与正交墙共享、不逐格归因,逐格指认 = 悬停金框(呈现交互归《HUD框架》悬停指示域)。验收:目检〈微光只画英雄格一侧接触面:正交邻墙整条边、斜向邻墙角点 + 半边〉 ←§2.1;§4.2;P1 - **墙体**:圆角砌块堆叠,禁尖刺装饰——尖刺专属危险语义,装饰禁止伪装逻辑差异。验收:目检〈墙体无尖刺装饰〉 ←原三 - **高墙瓦片体系|术语与定义**:未挖格 = **一整块 32×32 自含凸起岩体**;素材语义名——未开格素材 = **墙瓦片**,已开格素材 = **地面瓦片**。瓦片以自含岩体为单位,Tiny Dungeon 拼块套不进自含岩体定义,不作瓦片参考。验收:目检〈未开格为整块凸起岩体,无拼块接缝感〉 ←原三 @@ -513,7 +513,7 @@ ### 8.2 像素基准与画布 - **缩放纪律(硬规则,按资产两类分级;不做个案豁免)**: - - **甲类·文本与语义件**(数字、图标、封印印记、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 + - **甲类·文本与语义件**(数字、图标、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。封印印记不属本类(参数化绘制件,见本条末「封印印记参数表」)。砖面 tile 亦不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉 - **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉 - **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×、**护甲类型图标三枚 24px=1×**——源 24×24,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉 - **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉 @@ -522,7 +522,7 @@ - **画布基准(暂定值)**: - **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致); - **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销; - - **封印印记源档 = 32×32、显示 1×**(甲类整数倍纪律):形 = 外环+十字(形与画法见 §3.2)。定值——**外环外径 23px(半径 11.6px)、环壁厚 2.6px;十字两臂各宽 3.2px、四端止于环内沿半径 9px(与环相接);圆心=格心;最外沿距格边 ≥4px**(该 4px 空带归变异纹带,§3.2 变异格形状通道)。纯黑实心剪影,单图像素域色数 = 1(底色透明)。改形须本节与 §3.2 同批修订。验收:目检〈件为 32×32 单色;外沿距格边 ≥4px;十字四端与环相接〉 + - **封印印记参数表(绘制件,非贴图件)**:形与画法见 §3.2。定值——**环外径 = 0.82 × 格边**(26 格 21.3px/46 格 37.7px);**环壁厚 = 0.11 × 环径**(≈2.3/4.1px);**十字臂宽 = 0.156 × 环径**(≈3.3/5.9px);**环外光圈宽 = 0.09 × 格边**(≈2.3/4.1px),向外衰减指数 1.6、峰值 alpha 0.50;圆心 = 格心。色 = 封印黑单色(光圈只改 alpha)。本件随格边连续缩放,**不适用甲类整数倍纪律**(非贴图件,见上条清单)。参数单点 = `ui_kit.gd` 四枚常量 `SEAL_RING_FRAC`/`SEAL_WALL_FRAC`/`SEAL_ARM_FRAC`/`SEAL_HALO_ALPHA`,本节为条款侧对照值。改值须本节与 §3.2 同批修订。验收:机检〈ui_kit 四常量与本表逐值一致〉+目检〈渲染帧量环径落 82%±1px、光圈不越格边〉 - **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉 - **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉 - **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉 diff --git a/design/art/art-status-checklist.md b/design/art/art-status-checklist.md index 3f8ed430..8c203361 100644 --- a/design/art/art-status-checklist.md +++ b/design/art/art-status-checklist.md @@ -27,7 +27,7 @@ |---|---|---| | 格面资源徽章 19 枚 | ◐ | [art-bible](art-bible.md) §4.6 | | 关卡节点图图标组 | ◐ | [art-assets-checklist](art-assets-checklist.md) §3.2(正式 UI 设计未成文) | -| 雷/封印印记/数字砖面全套 | ◐ 封印印记已换代(环+十字法阵);雷与数字砖面仍待格子样张前置 | [art-bible](art-bible.md) §1.4/§3.2 | +| 雷/封印印记/数字砖面全套 | ◐ 封印印记已换代(环+十字法阵,参数化绘制);雷与数字砖面仍待格子样张前置 | [art-bible](art-bible.md) §1.4/§3.2 | | 增益状态效果图标 9 枚 | ✅ 已接入(2026-09-24) | [art-bible](art-bible.md) §4.6.1(候选原稿:状态图标批 2026-09-23 远程出图,归 candidates 档——路径描述性引用,归档纪律见 art-bible §8.3) | | 营地场景图 | ◐ | [art-bible](art-bible.md) §6.2 | | 字体(全局 UI 像素字,Fusion Pixel 单套单字重) | ✅ 已定稿在役(2026-09-27 接入;2026-10-24 用户裁现状即终稿——不另接拉丁专件、不上第二字重) | [art-bible](art-bible.md) §7.4 | @@ -38,7 +38,7 @@ |---|---|---| | 受击反馈 / 死亡表现 / 顺位标记 | ◐ 程序化演出在役(受击闪白+打击火花、灰化+碎裂退场〔禁瞬消〕、顺位徽标 ①②③ 程序绘制;帧级素材未产,§8.4 帧数规格候选) | [战斗表现](../gdd/systems/战斗循环/战斗表现.md) | | 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 | -| 封印特效(动作=封印,两件:施印特效+印记常显) | ◐ 印记常显已落位(`seal_mark.png` 环+十字);施印瞬间特效缺 | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | +| 封印特效(动作=封印,两件:施印特效+印记常显) | ◐ 印记常显已落位(着色器绘制);施印瞬间特效缺 | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | | 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) | | B 区舞台背景三关变体+共用连续地面层(场景件) | ✅ 已交付(2026-10-31 入包 `game/assets/art/scene/`:三张背景 1280×252、共用地面 1280×100;地面按作者裁保留候选 v12b 原亮度 L≈0.132) | [art-bible](art-bible.md) §6.1 · [battle-stage-prompts](battle-stage-prompts.md) §2 | diff --git a/待办清单.md b/待办清单.md index 2f0424ed..bf8877d6 100644 --- a/待办清单.md +++ b/待办清单.md @@ -190,17 +190,11 @@ #### 棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计(2026-10-30 登记,来源=会话提及) -**实况**:2026-11-04 讨论定案——形=**环+十字法阵**(外环=圈定,环内十字=钉住此格,四端顶到环内沿连成一体);色=封印黑 `#000000` 纯黑无光效(封印紫在封印黑批已退役、不并存);画法=纯黑实心块面、禁细线(32px 下细线断成碎点)。尺寸定值落 [美术圣经](design/art/art-bible.md) v3.38 §8.2「封印印记源档」(外径 23px/环壁 2.6px/十字臂 3.2px/外沿距格边 ≥4px),形与画法落 §3.2。终稿已同名替换 `game/assets/art/board/seal_mark.png`(32×32)。连带件=战末金蜡封徽记沿用同图(零代码改动);特效两件只做常显。消费面 `tile_palette.gd:19`、`board_view.gd:539`/`:582`、`battle_end_overlay.gd:84`。同批清圣经四处「旗」与 §4.1 旧形前提,[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 同步;圣经 :370/:540 两处「背包旗」属 character-design 命名,未动。 +**实况**:2026-11-04 定案——形=环+十字法阵;色=封印黑 `#000000`(光圈只加 alpha,不加色相);**画法改参数化绘制**:格边 26–46px 连续可变,贴图必非整数缩放,故由着色器按格边画环+十字。定值:环外径 82% 格宽、壁厚 11% 环径、臂宽 15.6% 环径、环外光圈宽 9% 格边(峰值 α0.50、不外溢邻格)。条款落 [美术圣经](design/art/art-bible.md) v3.41 §3.2 与 §8.2「封印印记参数表」,样张过审=2026-11-04 用户点头 82% 档。贴图 `seal_mark.png`(现为环+十字)退为战末金蜡封徽记专用;棋盘侧贴图绘制路径待实现批退役(`tile_palette.gd:19`、`cell_mark_overlay.gd:95-96`、`board_view.gd:541`)。[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 两行同步。 -**缺口**:还差两件:①实机目检(三关封印印记形状可读、与巢穴纹/变异纹带不同格混淆、战末蜡封观感);②施印瞬间特效未实装(清单 §4 两件中的第一件)。 +**改动面**:文档+美术素材+游戏代码 -**改动面**:文档+美术素材 - -**状态**:美术设计中 - -### 教学引导 - -> **节头注记(2026-10-04 用户拍板)**:教学引导定稿批整体改排 **Beta 阶段**——当前 Alpha 版不做教学相关工作,本节五条全部随之改排(2026-09-19 的 Alpha 排期拍板被本拍板取代)。同日五问讨论已形成初裁与设计提案,提案已归档 `archive/` 目录(磁盘留存、不入 git,Beta 批立项底稿,引用以其「归档状态与初裁记录」节为准)。初裁摘要:①教学形态=丙1 混合(首次冒险编排式首遇提示〔挂 first_script 锚、不锁操作〕+其余全程情景触发);②「因果教学顺序」问题**注销**——只教扫雷操作,结算读法不设教学(用户裁:结算只是表达、展现、演出,无策略内容;展示层每行带来源标注已自带归因;教学引导.md 旧前提〔设计目的句/P0 候选归因提示/验收第 2 条〕随 Beta 批文档定稿清除);③图例面板按循环段落分组、无结算段;④不做「再教一次」——改走独立「帮助」手册面(全部内容放一个文档);⑤教学文案内容权归设置与本地化(两文档负责表对齐随 Beta 批)。**未裁三疑点**(Beta 立项时先裁):增益叠层节奏条目去留/帮助入口位置(设置面为推荐)/图例面板与帮助手册的内容分工。 +**状态**:美术设计已闭合 #### 教学形态未定(2026-10-04 登记,来源=审计发现) From 12076b3671e38a3e6cd261d4ca6b13e5b3793c1f Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:43:43 +0800 Subject: [PATCH 09/19] =?UTF-8?q?=E5=86=99=E5=8D=A1(=E5=B0=81=E5=8D=B0?= =?UTF-8?q?=E5=8D=B0=E8=AE=B0=E6=B3=95=E9=98=B5=E5=8C=96=E6=89=B9):=20?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E5=8D=A1=E7=89=87=20v1.0=20+=20=E8=83=BD?= =?UTF-8?q?=E5=8A=9B=E6=B8=85=E5=8D=95=E5=A2=9E=E4=B8=80=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reports/需求卡片-封印印记法阵化批-2026-11-04.md: 十节结构齐(目标/设计事实源/现状 file:line/契约七条/范围/同步面五条/T1-T6 含文件结构图/验收对照表 R1-R7 含判据绑定面与能力行/异常分级/代拍板五条/零待裁点/落位复用预检/独立验收审计); 估时 3.75 工作日(区间 2.75-5.75) - game/tests/README.md: 排雷玩法段增能力行「棋盘封印印记呈现」, 代表用例 seal_mark_params_test 两函数(写卡段职责) - 待办清单本条状态 美术设计已闭合→美术设计已闭合+已写卡, 实况记卡片路径与估时 - art-bible §8.2 参数单点订正为五枚常量(补 SEAL_HALO_GAMMA), 与卡片 T1 对齐 - pipeline_gate PASS(8 张卡全过); 文档域闸与基线一致零新增红 --- design/art/art-bible.md | 2 +- game/tests/README.md | 1 + .../需求卡片-封印印记法阵化批-2026-11-04.md | 194 ++++++++++++++++++ 待办清单.md | 4 +- 4 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 reports/需求卡片-封印印记法阵化批-2026-11-04.md diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 4d6221a2..80c28c7e 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -522,7 +522,7 @@ - **画布基准(暂定值)**: - **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致); - **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销; - - **封印印记参数表(绘制件,非贴图件)**:形与画法见 §3.2。定值——**环外径 = 0.82 × 格边**(26 格 21.3px/46 格 37.7px);**环壁厚 = 0.11 × 环径**(≈2.3/4.1px);**十字臂宽 = 0.156 × 环径**(≈3.3/5.9px);**环外光圈宽 = 0.09 × 格边**(≈2.3/4.1px),向外衰减指数 1.6、峰值 alpha 0.50;圆心 = 格心。色 = 封印黑单色(光圈只改 alpha)。本件随格边连续缩放,**不适用甲类整数倍纪律**(非贴图件,见上条清单)。参数单点 = `ui_kit.gd` 四枚常量 `SEAL_RING_FRAC`/`SEAL_WALL_FRAC`/`SEAL_ARM_FRAC`/`SEAL_HALO_ALPHA`,本节为条款侧对照值。改值须本节与 §3.2 同批修订。验收:机检〈ui_kit 四常量与本表逐值一致〉+目检〈渲染帧量环径落 82%±1px、光圈不越格边〉 + - **封印印记参数表(绘制件,非贴图件)**:形与画法见 §3.2。定值——**环外径 = 0.82 × 格边**(26 格 21.3px/46 格 37.7px);**环壁厚 = 0.11 × 环径**(≈2.3/4.1px);**十字臂宽 = 0.156 × 环径**(≈3.3/5.9px);**环外光圈宽 = 0.09 × 格边**(≈2.3/4.1px),向外衰减指数 1.6、峰值 alpha 0.50;圆心 = 格心。色 = 封印黑单色(光圈只改 alpha)。本件随格边连续缩放,**不适用甲类整数倍纪律**(非贴图件,见上条清单)。参数单点 = `ui_kit.gd` 五枚常量 `SEAL_RING_FRAC`/`SEAL_WALL_FRAC`/`SEAL_ARM_FRAC`/`SEAL_HALO_ALPHA`/`SEAL_HALO_GAMMA`,本节为条款侧对照值。改值须本节与 §3.2 同批修订。验收:机检〈ui_kit 五常量与本表逐值一致〉+目检〈渲染帧量环径落 82%±1px、光圈不越格边〉 - **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉 - **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉 - **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉 diff --git a/game/tests/README.md b/game/tests/README.md index 88a00411..50156660 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -119,6 +119,7 @@ tests/ | 挖/封印合法性与可辨拒绝(拒绝零副作用) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `minesweeper_rules_test.gd:_test_dig_face_rules`;`minesweeper_rules_test.gd:_test_seal_place`;`minesweeper_rules_test.gd:_test_reject_out_of_catalog` | | 棋盘揭示预览只读口(零状态写入/与实挖揭示集同口径) | 排雷玩法 | `MinesweeperRules.preview_reveal()` | `minesweeper_rules_test.gd:_test_preview_reveal_readonly`〔架构整改 V14〕 | | 封印不可逆与挖开封印格放行(兑付改判四情况) | 排雷玩法 | `MinesweeperRules.settle_batch()` | `seal_polarity_test.gd:_smoke_seal_irreversible_quadrants`;`minesweeper_rules_test.gd:_test_seal_place` | +| 棋盘封印印记呈现 | 排雷玩法 | `BoardCellUnit.edge` → `SealMarkLayer.setup()` | `seal_mark_params_test.gd:_test_param_values`;`seal_mark_params_test.gd:_test_fog_gate` | | AP 扣费、末点耗尽即收轮与战斗收束清零 | 行动经济 | `MinesweeperRules.settle_batch()`(→`ActionEconomy.try_spend()`/`ActionEconomy.notify_combat_end()`) | `seal_polarity_test.gd:_smoke_seal_ap_exhausted`;`round_flow_skill_pool_test.gd:_smoke_full_battle`;`round_flow_skill_pool_test.gd:_smoke_active_refresh` | | 停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励) | 行动经济 | `BattleOrchestrator.player_request_refresh()`/`MinesweeperRules.settle_batch()` | `round_flow_skill_pool_test.gd:_smoke_active_refresh`;`seal_polarity_test.gd:_smoke_refresh_provenance_guard`;`refresh_feats_test.gd:test_no_feats_baseline` | | 换图系刷新链路与效果词表接线 | 行动经济×战斗结算×奇物 | `MinesweeperRules.settle_batch()`/`BattleOrchestrator.player_request_refresh()` | `effect_words_t1_test.gd:_init`;`forge_table_test.gd:_test_effect_kinds_closed`;`refresh_feats_test.gd:test_board_refresh_fact_only_on_success` | diff --git a/reports/需求卡片-封印印记法阵化批-2026-11-04.md b/reports/需求卡片-封印印记法阵化批-2026-11-04.md new file mode 100644 index 00000000..b9001962 --- /dev/null +++ b/reports/需求卡片-封印印记法阵化批-2026-11-04.md @@ -0,0 +1,194 @@ +# 需求卡片:封印印记法阵化批(2026-11-04) + +来源:待办清单「棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计」条目(**唯一事实源**:`design/art/art-bible.md` v3.41 §3.2「封印印记」条+§8.2「封印印记参数表」;设计闭合凭据=art-bible v3.41 落文+2026-11-04 用户点头 82% 档样张)。验收档:**中**(纯呈现层——零持久状态、零 RNG、零数值;做错的后果=棋盘观感错,玩家可见但不损存档)。状态:已写卡(v1.0,2026-11-04)。改动面:游戏代码(含随逻辑改的测试代码)。 + +## 1. 目标与完成定义 + +**这批做什么**:把棋盘侧封印印记从「32×32 贴图铺满整格」改为「着色器按格边参数化绘制」——环+十字+环外一圈压暗光,环径 82% 格宽,随格边 26–46px 连续缩放;贴图件仅战末金蜡封徽记继续使用。 + +| 面 | 现状 | 终态 | +|---|---|---| +| 绘制手段 | `draw_texture_rect(tex_seal, rect)` 铺满整格(28 格缩 0.875、46 格放 1.4375,均非整数) | 着色器按格边实时画(无缩放、任意格边清晰) | +| 形 | 环+十字(32px 贴图) | 环+十字(参数化:环径 82% 格宽/壁厚 11% 环径/臂宽 15.6% 环径) | +| 底光 | 无 | 环外一圈压暗光(宽 9% 格边、峰值 α0.50、衰减 1.6、不出格) | +| 贴图 | 棋盘与战末蜡封共用 `seal_mark.png` | 棋盘侧不再引用;蜡封继续用该贴图(`battle_end_overlay.gd:84`) | + +**什么状态算完**:五枚参数常量落 `ui_kit` 且有单元断言守门;着色器与层件落码并被 CellUnit 按格挂载;棋盘侧贴图引用路径全部退役且全仓零残留;三关渲染取证帧量出环径 82%±1px、光圈不越格边;迷雾态隐藏与小人层级两条不断;既有套件零转红;代码域闸全绿;`reports/` 三份产出物写齐。 + +## 2. 设计事实源 + +- **唯一事实源**:`design/art/art-bible.md` v3.41 §3.2「封印印记」条(形/画法/光圈/层序纪律)+ §8.2「封印印记参数表」(五枚定值+衰减+参数单点声明)。 +- **格边口径**:`design/gdd/ux/battle.md:137-143`「棋盘格尺寸公式」——`clampi(..., 26, 46)`,基座档 14×14=28px、小板最大 46px。 +- 该文件与待办清单条目状态里的闭合凭据一致(美术设计已闭合=art-bible v3.41+样张过审);T1 首步复核两者一致,冲突即 L3 停卡。 + +## 3. 现状事实(2026-11-04 worktree seal-shader 实查,基点 7b5308c1,开工自行复核) + +- `game/presentation/views/board_view.gd:539-541` 封印槽维护——`u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null`;`:542-545` 移除格清槽;`:70 var _cell := 46.0`;`:582` 缺图回落 `glyph = "封"`。 +- `game/presentation/views/cell_mark_overlay.gd:33` `var tex_seal: Texture2D`;`:95-96` `canvas.draw_texture_rect(tex_seal, rect, false)`——整格铺满,非整数缩放根源在此。 +- `game/presentation/views/tile_palette.gd:19` `const TILE_SEAL_MARK := "res://assets/art/board/seal_mark.png"`;`:111` `seal_tex = UiKit.load_tex(TILE_SEAL_MARK)`。 +- `game/presentation/views/board_cell_unit.gd:10` preload 微光层件;`:46 var _glow_layer`;`:58-63 _draw` 六步时序(`_draw_fog_layer` 在 `:69`,退色层在 `:64` 之后);`:90-100 _sync_glow_layer()` 是子件生命周期样板。 +- `game/presentation/views/cell_type_glow_layer.gd` 全件——子件模板:材质独占、`draw_rect` 全幅、`Color(1,1,1,1.0/255.0)` 非零 alpha 触发着色器、参数全取 ui_kit。 +- `game/presentation/views/ui_kit.gd:33` `SEAL_BLACK`;`:90-93` `TYPE_GLOW_ALPHA/BAND_FRAC/SOFT/CORNER`(参数单点先例)。 +- 取证面:`tools/render_gate.sh:107-114` 已挂 `board_shot_probe` L1–L3 棋盘抓帧;`game/probe/type_glow_shot_probe.gd` 是同类「单要素呈现」取证先例。 +- `game/presentation/views/battle_end_overlay.gd:84` 战末金蜡封印记直接读 `res://assets/art/board/seal_mark.png`(不走 `tile_palette` 常量)——本批不动它。 + +## 4. 设计契约(违反即返工红线) + +- 🔴 **参数五值**:环外径 `0.82`×格边、壁厚 `0.11`×环径、臂宽 `0.156`×环径、光圈宽 `0.09`×格边、光圈峰值 alpha `0.50`、衰减指数 `1.6`;单点=`ui_kit.gd` 五枚常量,着色器只读注入,代码内禁第二处字面值。 +- 🔴 **光圈不出格**:着色器内半径上限夹到 `cell_px/2`,禁越界到邻格。 +- 🔴 **色只用一枚**:印记与光圈都取 `SEAL_BLACK`(光圈只改 alpha);禁新增色、禁 ui_kit 之外裸 `Color8`(既有守卫)。 +- 🔴 **层序**:印记层挂 CellUnit 之下(低于 BoardView 层的小人精灵),且 `fog_state != 0`(区外全黑/记忆态)时整层隐藏——§6.5「封印随格隐没」。 +- 🔴 **禁动效**:不呼吸、不脉冲(§2.1 挖掘期唯一在动=接触带微光)。 +- 🔴 **贴图退役面**:棋盘侧三处引用(`tile_palette.gd:19/:111`、`cell_mark_overlay.gd:33/:95-96`、`board_view.gd:539-541/:582`)全部退役后,`game/` 内 `seal_mark` 只剩 `battle_end_overlay.gd:84` 一处引用,链上禁留旧常量别名或死槽。 +- 🔴 **测试纪律**:禁变异类测试、禁撤销类取证、禁删断言换绿(第 8 节申报之外零删改)。 + +## 5. 范围边界 + +**收件**:着色器文件、层件、CellUnit 挂载与生命周期、BoardView 下发、棋盘侧贴图三处退役、五枚常量、参数与雾门控断言、三关渲染取证。 + +**不收件**(去向已指认): +- 施印瞬间特效(清单 §4 两件中的第一件)——未实装,另立演出类条目时再做;本批只做常显形态。 +- 战末金蜡封徽记形制——继续用现有贴图,零改动。 +- 色板、其它格面元素(数字/徽章/变异纹带/类型微光)——零触碰。 +- `game/tests/README.md` 能力行——**新增一行「棋盘封印印记呈现(参数化绘制)」**(写卡段已随卡落,代表用例 `seal_mark_params_test.gd` 两函数;实现批禁写该文件)。 +- 数值表、MC、模拟——零跑(零数值改动)。 + +## 6. 关联文档同步面 + +实现批禁写 `design/` 与根规则文件;下表每条落文义务进 `reports/iter-反哺点-<日期>.md`,由用户裁决后归文档批落文: + +| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务(反哺点清单逐条承载) | +|---|---|---| +| design/art/art-bible.md §3.2/§8.2 | 已随美术设计闭合批落文(v3.41,条款即本批规格) | 实现期只核不改;若要改值=文档批,不在实现批 | +| 体验验证清单.md 第 4 条 | 判据现为「`seal_mark.png` 三关加载、非字形回退」,贴图退役后该判据失去对象 | 反哺点列改判据:「三关封印格呈环+十字、环径 82%±1px」 | +| design/art/asset-path-map.md | `seal_mark.png` 用途由「棋盘封印印记」改为「战末蜡封徽记专用」 | 反哺点列行号与改后表述 | +| game/tests/README.md | 能力行已由写卡段新增「棋盘封印印记呈现(参数化绘制)」一行 | 批末核对代表用例名与实现落地函数逐字一致(不一致则反哺点提出) | +| 术语表 | 零新增术语(环/十字/光圈均为现有词或本次条款已释义) | 确认行「零新增术语」 | + +(机检口径:本表同步义务格无一条写「随批」。) + +## 7. 任务卡 T1..T6 + +### 卡前·文件结构图 + +``` +新增 + game/presentation/views/seal_mark.gdshader 【行为改动 R1/R2/R3】环+十字+环外光圈,参数只读注入 + game/presentation/views/seal_mark_layer.gd 【行为改动 R1/R3/R4】子件承载(仿 cell_type_glow_layer) + game/tests/unit/presentation/seal_mark_params_test.gd 【非行为·新增】五常量与注入面断言 +改动 + game/presentation/views/ui_kit.gd 【非行为 R1】+五常量(值同 art-bible §8.2) + game/presentation/views/board_cell_unit.gd 【行为改动 R1/R4/R5】+封印子件挂载/生命周期/雾门控/edge 联动 + game/presentation/views/board_view.gd 【行为改动 R4】:539-541 槽位维护改下发封印层;:582 回退分支退役 + game/presentation/views/cell_mark_overlay.gd 【行为改动 R6】删 tex_seal 成员与 :95-96 绘制、头注改写 + game/presentation/views/tile_palette.gd 【行为改动 R6】删 TILE_SEAL_MARK 与 seal_tex + game/tests/run_all.gd 【非行为】注册新单元件 + game/probe/board_shot_probe.gd 或新增探针 【行为改动 R1/R3/R5】三关封印格取证帧 +不动 + game/presentation/views/battle_end_overlay.gd:84 战末蜡封继续读同一张 PNG + game/assets/art/board/seal_mark.png 素材在库保留(蜡封专用) +``` + +### T1 常量与参数断言(估时 0.25 工作日;区间 0.25–0.5) + +取用:无。产出:`UiKit.SEAL_RING_FRAC/SEAL_WALL_FRAC/SEAL_ARM_FRAC/SEAL_HALO_ALPHA/SEAL_HALO_GAMMA`(float 常量)+`seal_mark_params_test.gd`。 + +- [ ] 复核 art-bible v3.41 §8.2 参数表与本卡第 4 节五值逐字一致(不一致=L3 停卡) +- [ ] `ui_kit.gd` 邻近 `SEAL_BLACK`(:33) 处新增五常量,值 = `0.82 / 0.11 / 0.156 / 0.50 / 1.6`,各带一行来源注(引 §8.2) +- [ ] 新增 `tests/unit/presentation/seal_mark_params_test.gd`:断言五常量取值;断言 `seal_mark.gdshader` 文件存在且文本含 `cell_px/ring_frac/wall_frac/arm_frac/halo_frac/halo_alpha/halo_gamma/mark_col` 七个 uniform 名 +- [ ] `tests/run_all.gd` 注册该件(仿既有 `{"path": ..., "expect": 0}` 行) + +### T2 着色器本体(估时 1.0 工作日;区间 0.75–1.5) + +取用:T1 五常量。产出:`seal_mark.gdshader`(`shader_type canvas_item`),uniform 名单见 T1。 + +- [ ] 写 `seal_mark.gdshader`:以格中心为原点算半径 `r`;环体 = `ring_r-wall/2 ≤ r ≤ ring_r`(`ring_r = 0.5*cell_px*ring_frac`,`wall = 0.11*2*ring_r`);十字 = `(|dx| < arm/2 或 |dy| < arm/2) 且 r < ring_r`(`arm = arm_frac*2*ring_r`);光圈 = `r > ring_r` 区间按 `(1 - t)^halo_gamma * halo_alpha` 混黑,`t = (r-ring_r)/halo_w`,`halo_w = 0.5*cell_px*halo_frac`,并夹 `r ≤ 0.5*cell_px` +- [ ] 隔离验证:最小场景(单 32px/46px/28px 三个方块)直跑抓帧,确认三档格边下环径比例一致、光圈不越格(截图落 `artwork/` 在途目录,不入 git) +- [ ] 参数默认值仅作兜底(与 §8.2 同值),实际由 gd 侧逐次注入;代码内注释写明「禁在此改值」 + +### T3 层件与接入(估时 1.0 工作日;区间 0.75–1.5) + +取用:T1 常量、T2 着色器。产出:`seal_mark_layer.gd`+`BoardCellUnit` 挂载接口。 + +- [ ] 新增 `seal_mark_layer.gd`:`extends Node2D`;`setup(edge: float, on: bool) -> void` 内建 `ShaderMaterial` 并注入七参数(`mark_col = UiKit.SEAL_BLACK`);`_draw()` 用 `draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), Color(1,1,1,1.0/255.0))`(同微光层非零 alpha 手法) +- [ ] `board_cell_unit.gd`:`const _SEAL_LAYER := preload(...)`;`var _seal_layer: Node2D`;`func _sync_seal_layer() -> void`(on 才建、off 撤、edge 变更重注入);`edge` setter 与 `fog_state` setter 各追加一行联动 +- [ ] `board_view.gd:539-541` 改为写 CellUnit 的新公开属性 `seal_on: bool`(替代 `u.face.tex_seal` 赋值),`:542-545` 移除格同置 false +- [ ] 断言:单元件内新增 `_test_fog_gate`——造 CellUnit、置 `seal_on = true`、置 `fog_state = 1` → 断言封印子件不可见;`fog_state = 0` → 可见 + +### T4 棋盘侧贴图路径退役(估时 0.5 工作日;区间 0.25–0.75) + +取用:T3 接入完成(封印不再依赖贴图)。产出:三处引用清零。 + +- [ ] `cell_mark_overlay.gd`:删 `:33 tex_seal` 成员与其 setter、删 `:95-96` 绘制块、头注「④ 封印」段改写为「封印由 seal_mark_layer 子件绘制」 +- [ ] `tile_palette.gd`:删 `:19 TILE_SEAL_MARK` 与 `:111 seal_tex` 赋载;确认无第二消费者(`grep -rn "TILE_SEAL_MARK\|seal_tex" game/`) +- [ ] `board_view.gd`:删 `:539-541` 槽位维护语句与 `:582` 缺图回退分支,注释改写;确认 `battle_end_overlay.gd:84` 仍以字面路径读图(不受影响) +- [ ] 全仓 sweep:`grep -rn "seal_mark" game/ --include=*.gd` 只余 `battle_end_overlay.gd:84` 一处;把该 grep 结果写进实现报告作结构性证据 + +### T5 渲染取证与回归(估时 0.75 工作日;区间 0.5–1.0) + +取用:T3/T4 完成态。产出:三关封印格取证帧与读数表。 + +- [ ] 在 `board_shot_probe.gd` 增「封印格抓帧」分支(或新增 `seal_mark_shot_probe`):每关放一枚封印后抓整板帧,落盘带关名标签 +- [ ] 读数脚本(放 `tools/` 或探针内直算):从帧里量环外径像素 ÷ 格边长,输出三关读数 +- [ ] 判据落读数表:环径落 82%±1px;光圈外缘不超过格边(邻格边缘亮度与无封印帧一致) +- [ ] 回归:既有冒烟/单元/探针/渲染套件零转红(改前改后各一次,记录读数) + +### T6 产出物落位(估时 0.25 工作日;区间 0.25–0.5) + +- [ ] `reports/iter-实现报告-<日期>.md`:各卡状态+实机目检总清单(三关封印格观感、蜡封观感、封印格与变异纹带同格观感)+变异验证记录表(T1–T5 各条:变异点 file:行、跑的闸、红输出、还原结论)+遗留读数义务 +- [ ] `reports/iter-反哺点-<日期>.md`:第 6 节四条同步面逐条落(体验验证清单第 4 条改判据、asset-path-map 用途改述、能力清单零新增确认、术语零新增确认) +- [ ] `reports/iter-代拍板-<日期>.md`:按第 10 节白名单记录,全按卡执行则写「零拍板」 +- [ ] 轻闸全绿后、合并前复核三份齐备 + +**估时汇总**:0.25+1.0+1.0+0.5+0.75+0.25=**3.75 工作日**(区间 2.75–5.75)。低于 5 个工作日冻结线——编入迭代时按冻结制处理(凑批后转待开工)。 + +## 8. 验证闸(改动—验收对照表) + +| # | 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---|---| +| R1 | 环径 = 82% 格边,随格边连续 | `seal_mark.gdshader`(ring_frac 算环半径)+`ui_kit.gd` 五常量 | `seal_mark_params_test.gd:_test_param_values`(unit/presentation,尾缀 `_test`)+T5 三关帧量读数 | 单元+探针 | `BoardCellUnit.edge` → 层件 `setup()` → ShaderMaterial 参数 | 常量值逐项相等;三关帧读数「环外径像素 ÷ 格边」落 0.82±0.01 | 新增:棋盘封印印记呈现 | +| R2 | 壁厚 11% 环径、臂宽 15.6% 环径 | 同上 shader | `seal_mark_params_test.gd:_test_shape_ratios`+T5 帧量 | 单元+探针 | 同 R1 | 两常量逐值相等;帧上「环壁像素宽 ÷ 环外径」落 0.11±0.02 | 新增:棋盘封印印记呈现 | +| R3 | 光圈只在环外、不出格 | shader `halo_frac` 与 `r ≤ cell/2` 夹取 | `seal_mark_params_test.gd:_test_halo_clamp_present`+T5 邻格边缘亮度比对 | 单元+探针 | 同 R1 | shader 文本含夹取行;封印帧邻格边缘亮度与无封印帧差 ≤1/255 | 新增:棋盘封印印记呈现 | +| R4 | 迷雾态隐藏 | `board_cell_unit.gd` `fog_state` setter 联动 | `seal_mark_params_test.gd:_test_fog_gate` | 单元 | `BoardCellUnit.fog_state` 属性写入面 | `fog_state=1` 时封印子件 `visible == false`;`=0` 时 `true` | 新增:棋盘封印印记呈现 | +| R5 | 印记在小人之下 | 子件挂 CellUnit(BoardView 层 hero 精灵天然在上) | T5 帧取证 | 探针 | `board_view._place_hero` | 小人格中心像素 = 小人体色(非封印黑) | 新增:棋盘封印印记呈现 | +| R6 | 蜡封仍用贴图 | `battle_end_overlay.gd:84` 不动 | 既有战末弹层探针/单元零转红+`grep` sweep | 单元+sweep | 战末结算弹层构建 | `grep -rn "seal_mark" game/ --include=*.gd` 命中数 = 1 | 新增:棋盘封印印记呈现 | +| R7 | 零新增色 | shader 只用 `SEAL_BLACK`+alpha | `seal_mark_params_test.gd:_test_single_color`+渲染闸有效色数 | 单元+闸 | 材质注入点 | shader 文本零 `Color8` 且无第二色常量;该屏有效色数不超既有读数 | 新增:棋盘封印印记呈现 | + +**测试削弱申报**:本批不删改任何既有断言。`board_view.gd:582` 的缺图回退分支是**行为退役**(回退对象消失),不属「删断言」。 + +## 9. 异常分级 + +- **L1 自救**:着色器编译报错、参数注入拼写错、探针抓帧目录缺失——就地修,重跑最小单元。 +- **L2 记录继续**:取证帧读数轻微偏离(≤2px)且目检无感——记入实现报告「读数偏差」节,继续推进。 +- **L3 停机呈报**:参数与本卡第 4 节冲突、layer 挂载位置被迫改动、贴图退役引发图外消费者(白名单外)、渲染闸对新增着色器报红且无法定性。 + +## 10. 代拍板白名单 + +1. 常量与 uniform 的命名拼写(须含 `SEAL_` 前缀)、注释措辞。 +2. 探针文件名与抓帧文件命名(T5)。 +3. commit 拆分粒度与提交信息措辞。 +4. 单元件内断言函数命名与内部辅助函数拆分。 +5. 隔离验证用的最小场景形态(方块数、摆放)。 + +**超清单必呈报**:任何参数取值改动、层挂载层级改动、贴图退役范围扩大(例如顺手删 `seal_mark.png`)、动 `battle_end_overlay`、动其它格面元素。 + +## 11. 待裁点 + +**零待裁点**。开工门=U0(用户指定迭代号发令)。设计权归用户;本卡内所有取值均来自 art-bible v3.41 §8.2,实现期禁自改。 + +## 12. 落位与可复用性预检 + +| 新增件 | 复用面(盘上是否已有同类) | 唯一面 | 分层与交互 | +|---|---|---|---| +| `seal_mark.gdshader` | 复用 `cell_type_glow.gdshader` 的 canvas_item+距离场写法与「参数全由 gd 注入」纪律;不新建框架 | 只此一件承担封印图形;不与微光着色器合并(两者生命周期与挂载面不同:微光按组、封印按格态) | presentation 层;无 core 依赖;不读写存档/RNG | +| `seal_mark_layer.gd` | 复用 `cell_type_glow_layer.gd` 的子件模式(材质独占、全幅 draw_rect、非零 alpha 触发);不复制其函数体 | 只此一件承载封印子件;微光层件零改动 | 挂 `BoardCellUnit` 之下;受 `edge`/`seal_on`/`fog_state` 三输入驱动 | +| 五枚常量 | 复用 `ui_kit.gd` 既有常量区与 `TYPE_GLOW_*` 命名先例 | 全仓唯一定义点,着色器只读注入;代码内禁第二处字面值 | 参数单点,与 art-bible §8.2 对照 | +| `seal_mark_params_test.gd` | 复用 `tests/unit/presentation/` 目录约定与 `run_all.gd` 注册表 | 只此一件守封印参数与门控 | 单元层,不触渲染 | + +**无新增 core 件、无新增数据表、无新增场景**;`game/tests/README.md` 判定零新增(理由见第 5 节)。 + +## 13. 独立验收审计 + +- **入口**:`reports/iter-验收审计-<日期>.md`;由未参与实现的会话或子代理执行(test-conventions §9.7)。 +- **审计面**:第 8 节七行逐行核「真实入口是否绕过改动点」+代码域闸真跑回执+第 5 节退役面 sweep 结果。 +- **未过不得合并**;审计结论进实现报告收尾节。 diff --git a/待办清单.md b/待办清单.md index bf8877d6..e6758455 100644 --- a/待办清单.md +++ b/待办清单.md @@ -190,11 +190,11 @@ #### 棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计(2026-10-30 登记,来源=会话提及) -**实况**:2026-11-04 定案——形=环+十字法阵;色=封印黑 `#000000`(光圈只加 alpha,不加色相);**画法改参数化绘制**:格边 26–46px 连续可变,贴图必非整数缩放,故由着色器按格边画环+十字。定值:环外径 82% 格宽、壁厚 11% 环径、臂宽 15.6% 环径、环外光圈宽 9% 格边(峰值 α0.50、不外溢邻格)。条款落 [美术圣经](design/art/art-bible.md) v3.41 §3.2 与 §8.2「封印印记参数表」,样张过审=2026-11-04 用户点头 82% 档。贴图 `seal_mark.png`(现为环+十字)退为战末金蜡封徽记专用;棋盘侧贴图绘制路径待实现批退役(`tile_palette.gd:19`、`cell_mark_overlay.gd:95-96`、`board_view.gd:541`)。[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 两行同步。 +**实况**:2026-11-04 定案——形=环+十字法阵;色=封印黑 `#000000`(光圈只加 alpha,不加色相);**画法改参数化绘制**:格边 26–46px 连续可变,贴图必非整数缩放,故由着色器按格边画环+十字。定值:环外径 82% 格宽、壁厚 11% 环径、臂宽 15.6% 环径、环外光圈宽 9% 格边(峰值 α0.50、不外溢邻格)。条款落 [美术圣经](design/art/art-bible.md) v3.41 §3.2 与 §8.2「封印印记参数表」,样张过审=2026-11-04 用户点头 82% 档。贴图 `seal_mark.png`(现为环+十字)退为战末金蜡封徽记专用;棋盘侧贴图绘制路径待实现批退役(`tile_palette.gd:19`、`cell_mark_overlay.gd:95-96`、`board_view.gd:541`)。[美术资源依赖清单](design/art/art-assets-checklist.md) §3.3/§4 与 [美术素材状态清单](design/art/art-status-checklist.md) 两行同步。需求卡片 reports/需求卡片-封印印记法阵化批-2026-11-04.md(实现批输入合同,3.75 工作日)。 **改动面**:文档+美术素材+游戏代码 -**状态**:美术设计已闭合 +**状态**:美术设计已闭合+已写卡 #### 教学形态未定(2026-10-04 登记,来源=审计发现) From 90abdd4a10fa0cec51944f22f4453c07d8a74733 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:45:27 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E8=BD=A6=E9=81=93?= =?UTF-8?q?=E6=97=A7=E5=8F=A5=E6=B8=85=E6=89=AB):=20run=5Fall=5Fgates.sh?= =?UTF-8?q?=20.gitignore=20=E6=A8=A1=E6=9D=BF=E6=B3=A8=E9=87=8A=E3=80=8C?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=20CI=20=E5=88=A4=E5=AE=9A=E3=80=8D=E6=94=B9?= =?UTF-8?q?=E3=80=8CCI=20=E5=9B=9E=E6=89=A7=E5=88=A4=E5=AE=9A(=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E7=94=B1=E8=BF=9C=E7=AB=AF=20CI=20=E4=BA=A7=E7=94=9F)?= =?UTF-8?q?=E3=80=8D=E2=80=94=E2=80=94=E5=9B=9E=E6=89=A7=E7=8E=B0=E7=94=B1?= =?UTF-8?q?=20ci=5Fwatch=20remote=20=E8=BD=AE=E8=AF=A2=E8=BF=9C=E7=AB=AF?= =?UTF-8?q?=E5=86=99=E5=9B=9E,=E6=9C=AC=E5=9C=B0=E8=B7=91=E6=89=B9?= =?UTF-8?q?=E4=BB=B6=E5=B7=B2=E9=80=80=E5=BD=B9;=20=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E4=B8=8E=E8=A1=8C=E4=B8=BA=E9=9B=B6=E6=94=B9=E5=8A=A8,?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E5=8F=A3=E5=BE=84=E4=B8=8E=202026-11-03=20?= =?UTF-8?q?=E8=BF=9C=E7=AB=AF=E5=8C=96=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/run_all_gates.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index d2af73fc..2ad1dc41 100755 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -86,7 +86,7 @@ fi # 每次跑批前自愈重建,避免「清目录重跑」把 .gitignore 一起带走。 mkdir -p "$STAMP_DIR" if [ ! -f "$STAMP_DIR/.gitignore" ]; then - printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是本地 CI 判定与流水账、\n# ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。\nreceipt.json\nci-status.json\nci-history.jsonl\nci.lock\nci.rerun\n*.log\n' \ + printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是 CI 回执判定与流水账(判定由远端 CI 产生)、\n# ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。\nreceipt.json\nci-status.json\nci-history.jsonl\nci.lock\nci.rerun\n*.log\n' \ > "$STAMP_DIR/.gitignore" echo "[gates] 已自愈重建 $STAMP_DIR/.gitignore" fi From 3c1e3ddf07208f2a5d03f869c2aaa4ad9efc59c6 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 06:14:44 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B+=E6=96=87=E6=A1=A3(CI?= =?UTF-8?q?=20=E5=90=88=E5=9B=9E=E9=9B=B6=E7=AD=89=E5=BE=85):=20=E8=BD=AE?= =?UTF-8?q?=E8=AF=A2=E7=AD=89=E5=BE=85=E4=BB=B6=E4=B8=8E=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=9B=9E=E6=89=A7=E5=88=A4=E5=AE=9A=E9=80=80=E5=BD=B9=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E6=8C=89=E9=9C=80=E7=8E=B0=E6=9F=A5=20run=20=E7=BB=88?= =?UTF-8?q?=E5=B1=80=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 缘由(实证): ci_watch.sh 以「commit status 离开 pending」作终局判据——run 37 开跑 36 秒即写 verdict=red,该 run 三分半后 conclusion=success(现查 API),同 commit 先红后绿;远端时代 16 条 红回执所引 run 现查 15 条真红 1 条假红。另两条稳定假红源: 超时档(零触发批远端不建任务——现查 90abdd4a 的 statuses=null,只要挂了轮询必等满 3000s 写红;runner 排队同形)与多路轮询重复覆写 (fb27dc2f 三条红挤在 15 秒内)。修判据不如撤等待: 判据移到开工前一次现查,竞态与超时两类假红 结构性消失。 - tools/ci_check.sh(新): 单次读 run 自身 status/conclusion,未 completed 与零触发一律呈报未落定 不写红;红态列失败 job 与失败步;只呈报不落盘。退出码 0绿/1红/2未落定/3参数与令牌。 孤立验证四形态: fb27dc2f→红(run 51 conclusion=failure) / 90abdd4a→未落定(零触发) / f1840fbc→未落定(run 55 queued) / 39acdcaa→绿(run 48 success)。 - tools/hooks/post-merge: 只推送不再挂后台轮询;push_failed 判定不再落 ci-status.json,改大声呈报。 - 删 tools/ci_watch.sh 与 .zcode/hooks/ci-status-banner.sh(不入 git 面,主区直改)并从 .zcode/config.json 注销——该横幅在当前 harness 只挂 worktree-guard 的配置下本就不投递(本会话 ci-status 为 red 全程零横幅)。 - 口径同步: AGENTS.md 运行与验证首条、process-conventions §1 第3条(回执等待纪律→合回零等待纪律)与 第4条执法分层②、§8 第8条、test-conventions §3 与 §8.7 审计证据行(中/低档证据由落盘回执改为 可复核的现查读数=commit+run号+conclusion)、run_all_gates.sh 头注与 .gates gitignore 模板句、 迭代技能两镜像三处(入 git 面外另记 .zcode/.qoder)。 - 待办清单条目「ci_watch 中间态误写终局红回执」整条删除(以退役方式了结,非按原判据修复)。 - 在案真红一条随本批呈报未处理: run 51(迭代 51 合回 fb27dc2f) 失败步=导入缓存刷新+跑闸, 归因待取 job 日志——红未修不开新批的门按用户发令特批越过。 --- AGENTS.md | 2 +- design/engineering/process-conventions.md | 6 +- design/engineering/test-conventions.md | 6 +- game/tests/.gates/.gitignore | 2 +- tools/ci_check.sh | 100 +++++++++++++++++ tools/ci_watch.sh | 126 ---------------------- tools/hooks/post-merge | 24 ++--- tools/run_all_gates.sh | 8 +- 待办清单.md | 8 -- 9 files changed, 120 insertions(+), 162 deletions(-) create mode 100755 tools/ci_check.sh delete mode 100755 tools/ci_watch.sh diff --git a/AGENTS.md b/AGENTS.md index c5deaf5f..f4549c3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ ## 运行与验证(工作目录 = 含 project.godot 的切片目录) -- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。回执由 `tools/ci_watch.sh remote` 轮询 Gitea API 写回 `game/tests/.gates/ci-status.json`(合同不变:commit/绿红/红闸(job 级)+job 日志链接/耗时)+`ci-history.jsonl` 流水账;**回执等待一律后台(2026-11-02 用户裁)**:合回落地先向用户呈报本批总结,随即 run_in_background 运行 `tools/ci_watch.sh remote <合并提交>`,禁前台 sleep/轮询等 CI(细则=process-conventions §1 第 3 条)。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 +- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据只认 run 落定后的终局态,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 - 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd` - 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险 - 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`) diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index 157911e1..b4eeec4b 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -11,8 +11,8 @@ 1. **worktree 车道(一切面)**:每回合先建独立 worktree(建区粒度=回合,会话单区细则见第 5 条):`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) 2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑 -3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。回执由 `tools/ci_watch.sh remote` 轮询 Gitea API 写回 `game/tests/.gates/ci-status.json`(合同不变:commit/verdict/red_gates/key_lines/note),agent 开工先读 CI 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**回执等待纪律(2026-11-02 用户裁)**:合回落地即向用户呈报本批总结,回执等待一律以 run_in_background 后台任务运行 `tools/ci_watch.sh remote <合并提交>`(认领判据与容错细则=该件头注),禁前台 sleep/轮询循环等 CI;后台任务唤醒会话后按回执续行——绿即删区收口,红或超时即呈报走修复批。闸重跑等一切分钟级跑批等待同此纪律 -4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(推送+挂回执等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) +3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读该提交任务的 run 自身 `status`/`conclusion`,只呈报、不落盘),agent 开工先读 CI 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**合回零等待纪律(2026-11-04 用户裁,取代 2026-11-02 回执等待纪律)**:合回落地即向用户呈报本批总结并止回合——不挂后台轮询、不等回执、不被唤醒;CI 结论什么时候要看就现查一次(`tools/ci_check.sh`,run 那时早已落定,秒级)。退役缘由=原轮询件把「commit status 离开 pending」当终局判据,会把 run 未落定期的尝试级失败写成终局红(run 37 实证:开跑 36 秒写红,该 run 三分半后 `conclusion=success`),且零触发批与 runner 排队必熬满超时再写红;同 commit 的多路轮询还重复覆写同一份判定。跑闸等一切分钟级跑批的等待纪律不变——后台运行,禁前台 sleep/轮询循环。 +4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) 5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。 ## 2. 五段流水线 @@ -49,7 +49,7 @@ 7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补 **完成** -8. **合回后 CI 验证(唯一强制验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动推送 origin master 触发远端 CI;远端按改动域分流(§1 第 3 条),游戏码批跑全量域闸(不带参数档,02 全套件),判定与红项由 `ci_watch.sh remote` 写回 `game/tests/.gates/ci-status.json`。全量是游戏码批的强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。`tools/run_all_gates.sh` 是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时远端 CI 判红在案(job 失败=verdict 红,fail-closed 语义保留) +8. **合回后 CI 验证(唯一强制验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动推送 origin master 触发远端 CI;远端按改动域分流(§1 第 3 条),游戏码批跑全量域闸(不带参数档,02 全套件),判定与红项用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 终局态,只呈报不落盘)。全量是游戏码批的强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。`tools/run_all_gates.sh` 是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时远端 CI 判红在案(job 失败=verdict 红,fail-closed 语义保留) 9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复合回(post-merge 钩子再推再验,每轮修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。导入缓存刷新由远端工作流「导入缓存刷新」步接管,合并后无手工动作 10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12 11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index 34e25a54..a8fa1e4e 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -27,7 +27,7 @@ ## 4. 验证时机(合回后远端 CI 制,2026-11-03 换代) -开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、02 全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),回执由 `tools/ci_watch.sh remote` 轮询写回 `game/tests/.gates/ci-status.json`;CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1;本地跑批件 ci_runner.sh 与本地追绿钩子 post-commit 已退役)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 +开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、02 全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 自身 `status`/`conclusion`,只呈报、不落盘——合回不等待、不唤醒,process-conventions §1 第 3 条);CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1;本地跑批件 ci_runner.sh 与本地追绿钩子 post-commit 已退役)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。 ## 5. 测试函数怎么写——验收清单条目的落码规范 @@ -128,7 +128,7 @@ 3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。 4. 核对内部字段直查的受控例外:直查行是否已在对照表注明理由(该行为无公开可观测面可钉)。 -**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以 CI 回执为执行证据(回执是机器产物——「不采信实现者自述已全绿」的口径不变): +**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变): | 审计动作 | 高 | 中 | 低 | |---|---|---|---| @@ -136,7 +136,7 @@ | 判据绑定面核对 | 抽查(现行口径) | 抽查(现行口径) | 抽 1 行 | | 测试削弱检查 | 逐处 | 逐处 | 逐处 | | 直查例外核对 | 逐条 | 逐条 | 逐条 | -| 最窄测试复跑 | 抽查(现行口径) | 省略——以 CI 回执代证 | 省略——以 CI 回执代证 | +| 最窄测试复跑 | 抽查(现行口径) | 省略——以远端 CI 现查读数代证 | 省略——以远端 CI 现查读数代证 | 低档的抽查面与抽中依据照写审计报告;「每批必有审计、禁零审计批」不变;档位存疑按高档执行。 diff --git a/game/tests/.gates/.gitignore b/game/tests/.gates/.gitignore index 7bba17d4..7f5a2833 100644 --- a/game/tests/.gates/.gitignore +++ b/game/tests/.gates/.gitignore @@ -1,5 +1,5 @@ # 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。 -# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是本地 CI 判定与流水账、 +# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是已退役的 CI 回执判定与流水账(判定只在远端,本机残留)、 # ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。 receipt.json ci-status.json diff --git a/tools/ci_check.sh b/tools/ci_check.sh new file mode 100755 index 00000000..5ec916a7 --- /dev/null +++ b/tools/ci_check.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# CI 现查件——按需读远端 run 的终局态,只呈报、不落盘(2026-11-04 用户裁:回执等待件与本地缓存判定退役)。 +# 用法: tools/ci_check.sh [<提交hash>](缺省=当前 HEAD) +# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion—— +# run 未 completed(排队/在跑/尝试级中间态)与零触发批(纯闸件/管线批不建任务)一律呈报「未落定」,不写红。 +# 调用面:开工前读态(红未修不开新批)、合回后想知道结果时手动查——合回流程不等待、不唤醒,呈报即回合止。 +# 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。 +# 退出码: 0=绿 1=红 2=未落定/零触发/查不到 3=参数或令牌错误 +set -u +COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ + || { echo "不在 git 仓库内"; exit 3; } +REPO="$(dirname "$COMMON")" + +BASE="${1:-}" +[ -n "$BASE" ] || BASE="$(git -C "$REPO" rev-parse HEAD)" +BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; } + +_origin="$(git -C "$REPO" remote get-url origin 2>/dev/null || true)" +REPO_SLUG="$(printf '%s' "$_origin" | grep -oE '[^/:]+/[^/]+\.git$' | sed 's/\.git$//')" +[ -n "$REPO_SLUG" ] || { echo "无法从 origin 解析 owner/repo: $_origin"; exit 3; } + +TOKEN="${GITEA_TOKEN:-}" +if [ -z "$TOKEN" ]; then + TOKEN="$(printf 'protocol=https\nhost=git.kuaimeo.com\n\n' | git credential fill 2>/dev/null | sed -n 's/^password=//p' | head -1)" +fi +[ -n "$TOKEN" ] || { echo "取不到 Gitea 令牌(设 GITEA_TOKEN 或先 git credential approve 存储)"; exit 3; } +API="https://git.kuaimeo.com/api/v1/repos/$REPO_SLUG" +WEB="https://git.kuaimeo.com/$REPO_SLUG" + +export PYTHONIOENCODING=utf-8 +python -I - "$API" "$WEB" "$BASE" "$TOKEN" <<'PYEOF' +import json, re, sys, urllib.request, urllib.error + +api, web, base, token = sys.argv[1:5] +H = {"Authorization": "token " + token} + + +def get(url, t=20): + req = urllib.request.Request(url, headers=H) + return json.load(urllib.request.urlopen(req, timeout=t)) + + +def short(sha): + return sha[:8] + + +def undetermined(msg): + print("CI现查: 未落定 | commit %s | %s" % (short(base), msg)) + sys.exit(2) + + +try: + s = get(api + "/commits/%s/status" % base) +except urllib.error.HTTPError as e: + undetermined("查 commit status 失败 HTTP %s" % e.code) +except Exception as e: + undetermined("查 commit status 失败:%s" % e) + +statuses = s.get("statuses") or [] +target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "") +if not statuses: + undetermined("远端无该提交的任务——零触发批(纯闸件/管线批不建任务),或推送未触发 CI") + +m = re.search(r"/actions/runs/(\d+)", target or "") +if not m: + undetermined("commit status 无 run 链接(state=%s),查 %s/actions" % (s.get("state"), web)) +run_id = m.group(1) + +try: + run = get(api + "/actions/runs/%s" % run_id) +except Exception as e: + undetermined("查 run %s 失败:%s" % (run_id, e)) + +rstatus, rconclusion = run.get("status"), run.get("conclusion") +if rstatus != "completed": + undetermined("run %s status=%s(runner 需开机在线;队列与日志见 %s/actions)" % (run_id, rstatus, web)) + +if rconclusion == "success": + print("CI现查: 绿 | commit %s | run %s conclusion=success" % (short(base), run_id)) + sys.exit(0) + +red_jobs = [] +try: + jobs = get(api + "/actions/runs/%s/jobs" % run_id) + for j in jobs.get("jobs", []): + if j.get("conclusion") in ("success", "skipped", None): + continue + bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")] + red_jobs.append((j.get("name"), bad_steps)) +except Exception as e: + red_jobs.append(("" % e, [])) + +print("CI现查: 红 | commit %s | run %s conclusion=%s" % (short(base), run_id, rconclusion)) +for name, steps in red_jobs: + print(" 红 job:%s" % name) + for st in steps: + print(" 失败步:%s" % st) +print(" 日志:%s" % (target or "%s/actions/runs/%s" % (web, run_id))) +sys.exit(1) +PYEOF diff --git a/tools/ci_watch.sh b/tools/ci_watch.sh deleted file mode 100755 index 13b2b5e9..00000000 --- a/tools/ci_watch.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -# 回执等待件——合回后等远端 CI 回执的后台轮询器(2026-11-03 用户裁:CI 迁远端,本件改轮询 Gitea API)。 -# 用法: tools/ci_watch.sh remote <推送提交hash> [超时秒,默认 3000] -# 调用面: post-merge 钩子推送 master 成功后自动后台挂起;人手等待亦可(agent 会话以 run_in_background 运行, -# 命令结束时 harness 唤醒——绿→删区收口;红→呈报红闸名单走修复批;超时→呈报「回执未落」)。 -# 「本次回执已落」判据:Gitea commit status 的 state 离开 pending(success/failure/error), -# 且 status 的 target_url 指向本仓库 Actions(防认错仓)。远端跑批 20-35 分钟量级,默认超时给足 50 分钟。 -# 产物:沿用本地 CI 时代的回执合同——game/tests/.gates/ci-status.json(commit/verdict/timestamp/ -# duration_s/red_gates/key_lines/note)覆写 + ci-history.jsonl 追加;红态 red_gates=失败的 job 名单 -# (来自 runs//jobs API),job 日志链接在 key_lines,细粒度红闸清单以 job 日志为准。 -# 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。 -# 退出码: 0=绿 1=红 2=超时/回执未落 3=参数错误 -set -u -COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ - || { echo "不在 git 仓库内"; exit 3; } -REPO="$(dirname "$COMMON")" -GATES_DIR="$REPO/game/tests/.gates" -STATUS="$GATES_DIR/ci-status.json" -HISTORY="$GATES_DIR/ci-history.jsonl" - -MODE="${1:-}" -BASE="${2:-}" -TIMEOUT="${3:-3000}" -INTERVAL=30 -case "$MODE" in - remote) ;; - *) - echo "用法: ci_watch.sh remote <推送提交hash> [超时秒](本地 CI 模式已退役——回执现由远端产生)" - exit 3 ;; -esac -[ -n "$BASE" ] || { echo "用法: ci_watch.sh remote <推送提交hash> [超时秒]"; exit 3; } -BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; } - -# 仓库全名(owner/repo)从 origin URL 解析(HTTPS 与 SSH 形式都兼容) -_origin="$(git -C "$REPO" remote get-url origin 2>/dev/null || true)" -REPO_SLUG="$(printf '%s' "$_origin" | grep -oE '[^/:]+/[^/]+\.git$' | sed 's/\.git$//')" -[ -n "$REPO_SLUG" ] || { echo "无法从 origin 解析 owner/repo: $_origin"; exit 3; } - -# 令牌:环境变量优先,回落 git 凭据库 -TOKEN="${GITEA_TOKEN:-}" -if [ -z "$TOKEN" ]; then - TOKEN="$(printf 'protocol=https\nhost=git.kuaimeo.com\n\n' | git credential fill 2>/dev/null | sed -n 's/^password=//p' | head -1)" -fi -[ -n "$TOKEN" ] || { echo "取不到 Gitea 令牌(设 GITEA_TOKEN 或先 git credential approve 存储)"; exit 3; } -API="https://git.kuaimeo.com/api/v1/repos/$REPO_SLUG" - -export PYTHONIOENCODING=utf-8 -python -I - "$API" "$BASE" "$TOKEN" "$TIMEOUT" "$INTERVAL" "$STATUS" "$HISTORY" <<'PYEOF' -import json, os, re, subprocess, sys, time, urllib.request, urllib.error, datetime - -api, base, token, timeout, interval, status_path, history_path = sys.argv[1:8] -timeout, interval = int(timeout), int(interval) -deadline = time.time() + timeout -start = time.time() -H = {"Authorization": "token " + token} - - -def get(url, t=20): - req = urllib.request.Request(url, headers=H) - return json.load(urllib.request.urlopen(req, timeout=t)) - - -def combined_status(sha): - return get(api + "/commits/%s/status" % sha) - - -def write_receipt(verdict, note, red_gates, key_lines): - now = datetime.datetime.now().astimezone().isoformat(timespec="seconds") - dur = int(time.time() - start) - d = {"commit": base, "verdict": verdict, "timestamp": now, - "duration_s": dur, "note": note, "red_gates": red_gates, "key_lines": key_lines} - os.makedirs(os.path.dirname(status_path), exist_ok=True) - tmp = status_path + ".tmp" - with open(tmp, "w", encoding="utf-8") as f: - json.dump(d, f, ensure_ascii=False, indent=1) - os.replace(tmp, status_path) - with open(history_path, "a", encoding="utf-8") as f: - f.write(json.dumps(d, ensure_ascii=False) + "\n") - return d - - -def emit(d): - print("CI回执:", d.get("verdict"), "| commit", d.get("commit", "")[:8], "| 耗时", d.get("duration_s"), "s") - if d.get("note"): - print("note:", d["note"]) - for g in d.get("red_gates", []): - print("红闸:", g) - for l in d.get("key_lines", []): - print("key:", l) - - -last_err = "" -while True: - try: - s = combined_status(base) - state = s.get("state", "") - statuses = s.get("statuses", []) - target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "") - if state in ("success", "failure", "error", "warning"): - red = state != "success" - red_gates, run_id = [], None - m = re.search(r"/actions/runs/(\d+)", target) - if m and red: - run_id = m.group(1) - try: - jobs = get(api + "/actions/runs/%s/jobs" % run_id) - red_gates = sorted({j["name"] for j in jobs.get("jobs", []) if j.get("status") not in ("success", "skipped")}) - except Exception as e: - red_gates = ["" % e] - note = "远端CI state=%s%s" % (state, ("(run %s)" % run_id) if run_id else "") - key_lines = ["job日志: %s" % target] if target else [] - d = write_receipt("green" if not red else "red", note, red_gates, key_lines) - emit(d) - sys.exit(0 if not red else 1) - # pending:继续等 - except urllib.error.HTTPError as e: - last_err = "HTTP %s" % e.code - except Exception as e: - last_err = str(e) - if time.time() > deadline: - note = "超时(%ds)远端回执未落——最后错误 %s;查 runner 在线态与 %s/actions" % (timeout, last_err or "无", api.rsplit("/repos/", 1)[0]) - d = write_receipt("red", note, ["<回执未落>"], []) - emit(d) - sys.exit(2) - time.sleep(interval) -PYEOF diff --git a/tools/hooks/post-merge b/tools/hooks/post-merge index f0ca94c6..e3d26782 100644 --- a/tools/hooks/post-merge +++ b/tools/hooks/post-merge @@ -1,27 +1,19 @@ #!/usr/bin/env bash -# 合回验证点(远端 CI 触发钩子,2026-11-03 用户裁)——主工作区 git merge 落地后: -# ① push origin master(触发远端 CI:Gitea 收到 push → 派发 mac-ci runner 按改动域跑闸) -# ② 后台挂 ci_watch.sh remote 等回执(回执落 ci-status.json,agent 下次开工读态零改动) -# 推送失败=远端验证不可能发生——大声呈报并落 push_failed 判定进 ci-status.json(防静默,下游开工读态即见)。 +# 合回验证触发件(远端 CI 推送钩子,2026-11-03 用户裁;2026-11-04 用户裁去掉回执等待)。 +# 主工作区 git merge 落地后做一件事:`git push origin master` 触发远端 CI(Gitea Actions, +# runner=Mac 本机 host 模式,按改动域分流跑闸)。判定结果不在本钩子取——合回即呈报本批总结, +# 回合止;要看结果随时跑 tools/ci_check.sh <合并提交>(只呈报、不落盘,2026-11-04 取代原后台轮询件)。 +# 推送失败=远端验证不可能发生——大声呈报并给出处置(先修推送再手动 git push,之后用现查件读态),禁静默。 # 任务 worktree 内的合并不触发(CI 只认 master);CI 红了不回滚合并——修复批合回后本钩子再推再验(追绿同链)。 set -u _GD="$(cd "$(git rev-parse --git-dir)" && pwd)" _CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)" [ "$_GD" = "$_CD" ] || exit 0 -REPO="$(git rev-parse --show-toplevel)" +REPO="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || pwd)" TIP="$(git -C "$REPO" rev-parse HEAD)" -export PYTHONIOENCODING=utf-8 if ! git -C "$REPO" push origin master >/dev/null 2>&1; then - echo "[post-merge] ✗ push origin master 失败——远端 CI 无法触发!先修推送(凭据/网络)后手动 git push,再手动挂 tools/ci_watch.sh remote $TIP" - python -I -c " -import json, os, datetime -p = r'$REPO/game/tests/.gates/ci-status.json' -d = {'commit': r'$TIP', 'verdict': 'push_failed', 'timestamp': datetime.datetime.now().astimezone().isoformat(timespec='seconds'), - 'duration_s': 0, 'note': 'post-merge 推送失败,远端 CI 未触发——修复推送后手动 git push + ci_watch remote', 'red_gates': [], 'key_lines': []} -os.makedirs(os.path.dirname(p), exist_ok=True) -json.dump(d, open(p, 'w', encoding='utf-8'), ensure_ascii=False, indent=1) -" 2>/dev/null + echo "[post-merge] ✗ push origin master 失败——远端 CI 无法触发!先修推送(凭据/网络),再手动 git push,然后用 tools/ci_check.sh $TIP 读态。" exit 0 fi -( nohup bash "$REPO/tools/ci_watch.sh" remote "$TIP" >/dev/null 2>&1 & ) +echo "[post-merge] ✓ 已推送 origin master(commit ${TIP:0:8})——远端 CI 已触发;结果不等待,随时用 tools/ci_check.sh ${TIP:0:8} 现查。" exit 0 diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index 2ad1dc41..3607368f 100755 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -4,9 +4,9 @@ # 漏跑 probe_gate --full 的成因:清单散落三份文档、彼此不一致、且无机器校验)。 # # 跑完写回执 game/tests/.gates/receipt.json(commit+内容树哈希+各闸退出码+存量基线比对+时间戳); -# 远端 CI(2026-11-03 用户裁)读回执判定,回执经 tools/ci_watch.sh remote 轮询 Gitea API 写 ci-status.json—— -# 强制验证点=合回 master 后 post-merge 钩子推送触发的远端跑批;本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。 -# 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节),CI 判定照常落账留痕。 +# 强制验证点=合回 master 后 post-merge 钩子推送触发的远端跑批;远端结果用 tools/ci_check.sh 现查(只呈报、 +# 不落盘——2026-11-04 用户裁去掉回执等待件与本地判定缓存)。本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。 +# 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节)。 # # 用法:tools/run_all_gates.sh [--code|--docs|--quick] # --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 场景探针/ @@ -86,7 +86,7 @@ fi # 每次跑批前自愈重建,避免「清目录重跑」把 .gitignore 一起带走。 mkdir -p "$STAMP_DIR" if [ ! -f "$STAMP_DIR/.gitignore" ]; then - printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是 CI 回执判定与流水账(判定由远端 CI 产生)、\n# ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。\nreceipt.json\nci-status.json\nci-history.jsonl\nci.lock\nci.rerun\n*.log\n' \ + printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是已退役的 CI 回执判定与流水账(判定只在远端,本机残留)、\n# ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。\nreceipt.json\nci-status.json\nci-history.jsonl\nci.lock\nci.rerun\n*.log\n' \ > "$STAMP_DIR/.gitignore" echo "[gates] 已自愈重建 $STAMP_DIR/.gitignore" fi diff --git a/待办清单.md b/待办清单.md index e6758455..4956edfb 100644 --- a/待办清单.md +++ b/待办清单.md @@ -280,11 +280,3 @@ **改动面**:游戏代码 **状态**:待文档设计 - -#### ci_watch 中间态误写终局红回执(2026-11-04 登记,来源=批次移交) - -**实况**:2026-11-04 run 37(commit 536dc072)实证——ci-status.json 于 16:59:55 写入 verdict=red(note=远端CI state=failure, run 37),而该 run 实际 17:03:10 才以「Job succeeded」落定,现查 Gitea API 组合状态=success、run conclusion=success;回执与真值相反,下游按「红未修不开新批」被误阻塞(验收档与条目限长批开工时人工查 API 解封)。证据=job 日志结尾「Job succeeded」(Gitea API actions/jobs/38/logs)+commits/536dc072/status 现查 success。修复方向=轮询只认 run conclusion 落定后的终局态(pending/中间态不写盘继续等)。 - -**改动面**:工具代码 - -**状态**:待执行 From b9c2e86d137e838dff3ba7f659aeaf12f6ab37ae Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 06:29:18 +0800 Subject: [PATCH 12/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B+=E6=96=87=E6=A1=A3(CI?= =?UTF-8?q?=20=E7=8E=B0=E6=9F=A5=E4=BB=B6=E5=A0=B5=E5=81=87=E7=BB=BF):=20?= =?UTF-8?q?=E7=BB=BF=E9=A1=BB=E5=9D=90=E5=AE=9E=E8=B7=91=E9=97=B8=E6=AD=A5?= =?UTF-8?q?=E7=9C=9F=E8=B7=91=E8=BF=87=EF=BC=8C=E9=9B=B6=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E6=89=B9=E5=91=88=E6=8A=A5=E3=80=8C=E6=9C=AA=E9=AA=8C=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实证: run 55(零触发批 f1840fbc)的 job 步级读数=检出与域分流 success、导入缓存刷新与跑闸两步 skipped,而 run conclusion 仍为 success;原 ci_check.sh 只看 conclusion,把「一条闸都没跑」 读成绿。假绿比假红危险——它放行的是「红未修不开新批」这道开工门。 - tools/ci_check.sh: 绿分支改先取 jobs 再判——跑闸步全 skipped → 呈报「未验」退出码 4;job 列表 为空/取不到步级读数/工作流里查不到跑闸步 → 呈报「不可判」退出码 2,一律禁当绿证;红态读数 在 conclusion 与步级矛盾时显式报矛盾行。退出码表加 4=未验。 - 孤立验证五形态(真实远端读数): 3de8955f/run54 全步真跑→绿 exit0;f1840fbc/run55 跑闸步 skipped →未验 exit4;fb27dc2f/run51→红 exit1;5d7579e7/run56 in_progress→未落定 exit2;3c1e3ddf 未推 提交→无 status→未落定 exit2。 - 口径同步: AGENTS.md 运行与验证首条判据句、process-conventions §1 第 3 条加「绿须坐实」段、 test-conventions §8.7 中/低档证据句加「未验与不可判不作证据」+回补途径、迭代技能两镜像现查句 (不入 git 面)。 - 闸: --docs 验收通过零新增红(跑批树内容=本提交内容)。 --- AGENTS.md | 2 +- design/engineering/process-conventions.md | 2 +- design/engineering/test-conventions.md | 2 +- tools/ci_check.sh | 46 ++++++++++++++++------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f4549c3d..38630876 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ ## 运行与验证(工作目录 = 含 project.godot 的切片目录) -- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据只认 run 落定后的终局态,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 +- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据=run 落定后的终局态,且绿须坐实跑闸步真跑过——零触发批呈报「未验」不作绿证,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 - 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd` - 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险 - 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`) diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index b4eeec4b..3ed32191 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -11,7 +11,7 @@ 1. **worktree 车道(一切面)**:每回合先建独立 worktree(建区粒度=回合,会话单区细则见第 5 条):`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) 2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑 -3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读该提交任务的 run 自身 `status`/`conclusion`,只呈报、不落盘),agent 开工先读 CI 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**合回零等待纪律(2026-11-04 用户裁,取代 2026-11-02 回执等待纪律)**:合回落地即向用户呈报本批总结并止回合——不挂后台轮询、不等回执、不被唤醒;CI 结论什么时候要看就现查一次(`tools/ci_check.sh`,run 那时早已落定,秒级)。退役缘由=原轮询件把「commit status 离开 pending」当终局判据,会把 run 未落定期的尝试级失败写成终局红(run 37 实证:开跑 36 秒写红,该 run 三分半后 `conclusion=success`),且零触发批与 runner 排队必熬满超时再写红;同 commit 的多路轮询还重复覆写同一份判定。跑闸等一切分钟级跑批的等待纪律不变——后台运行,禁前台 sleep/轮询循环。 +3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读该提交任务的 run 自身 `status`/`conclusion`,只呈报、不落盘),agent 开工先读 CI 状态的纪律不变,红未修不开新批。**绿须坐实**:零触发批的 run 照样建、照样 conclusion=success,但跑闸步是 `skipped`(run 55 实证)——一条闸都没跑不构成验证,现查件对此呈报「未验」(退出码 4),禁当绿证用;job 列表为空、取不到步级读数或工作流里没有跑闸步时呈报「不可判」,同样禁当绿证。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**合回零等待纪律(2026-11-04 用户裁,取代 2026-11-02 回执等待纪律)**:合回落地即向用户呈报本批总结并止回合——不挂后台轮询、不等回执、不被唤醒;CI 结论什么时候要看就现查一次(`tools/ci_check.sh`,run 那时早已落定,秒级)。退役缘由=原轮询件把「commit status 离开 pending」当终局判据,会把 run 未落定期的尝试级失败写成终局红(run 37 实证:开跑 36 秒写红,该 run 三分半后 `conclusion=success`),且零触发批与 runner 排队必熬满超时再写红;同 commit 的多路轮询还重复覆写同一份判定。跑闸等一切分钟级跑批的等待纪律不变——后台运行,禁前台 sleep/轮询循环。 4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) 5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index a8fa1e4e..ffc6c41a 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -128,7 +128,7 @@ 3. 检查测试削弱:删除断言、放宽阈值、缩小扫描面、换下层直调,逐处要卡片申报或用户批准。 4. 核对内部字段直查的受控例外:直查行是否已在对照表注明理由(该行为无公开可观测面可钉)。 -**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变): +**审计深度分级(2026-11-04 立)**:审计按卡内申报的验收档(§8.3)逐卡定深度;梯度只落在昂贵的判断件上,机械且堵造假向量的两件(削弱检查、例外核对)三档全保留不抽。中/低档省略的测试复跑以远端 CI 读数为执行证据——`tools/ci_check.sh <合并提交>` 的现查输出(commit+run 号+conclusion)是机器产物,run 号在远端永久可复核,审计报告照抄该行即可;照抄前先核对读数必须是「绿」——「未验」(零触发批跑闸步全跳过)与「不可判」不作证据,那种批的回补办法是等一次含代码面的合回或用户自发全量回归(2026-11-04 用户裁:回执等待件退役,证据由落盘文件改为现查读数;「不采信实现者自述已全绿」的口径不变): | 审计动作 | 高 | 中 | 低 | |---|---|---|---| diff --git a/tools/ci_check.sh b/tools/ci_check.sh index 5ec916a7..28ded23c 100755 --- a/tools/ci_check.sh +++ b/tools/ci_check.sh @@ -1,11 +1,12 @@ #!/usr/bin/env bash # CI 现查件——按需读远端 run 的终局态,只呈报、不落盘(2026-11-04 用户裁:回执等待件与本地缓存判定退役)。 # 用法: tools/ci_check.sh [<提交hash>](缺省=当前 HEAD) -# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion—— -# run 未 completed(排队/在跑/尝试级中间态)与零触发批(纯闸件/管线批不建任务)一律呈报「未落定」,不写红。 +# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion, +# 且绿必须坐实「跑闸步真跑过」——零触发批的 run 照样建、照样 success,但跑闸步是 skipped, +# 一条闸都没跑,读成绿就是假绿(2026-11-04 run 55 实证)。run 未 completed(排队/在跑/尝试级中间态)呈报未落定。 # 调用面:开工前读态(红未修不开新批)、合回后想知道结果时手动查——合回流程不等待、不唤醒,呈报即回合止。 # 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。 -# 退出码: 0=绿 1=红 2=未落定/零触发/查不到 3=参数或令牌错误 +# 退出码: 0=绿(跑闸步真跑过) 1=红 2=未落定/不可判/查不到 3=参数或令牌错误 4=未验(零触发批,跑闸步全跳过) set -u COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ || { echo "不在 git 仓库内"; exit 3; } @@ -49,6 +50,11 @@ def undetermined(msg): sys.exit(2) +def unknown(msg): + print("CI现查: 不可判 | commit %s | %s" % (short(base), msg)) + sys.exit(2) + + try: s = get(api + "/commits/%s/status" % base) except urllib.error.HTTPError as e: @@ -59,7 +65,7 @@ except Exception as e: statuses = s.get("statuses") or [] target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "") if not statuses: - undetermined("远端无该提交的任务——零触发批(纯闸件/管线批不建任务),或推送未触发 CI") + undetermined("远端无该提交的 status——该批没推上去(或推的不是 master);零触发批有 run,走 run 侧读数") m = re.search(r"/actions/runs/(\d+)", target or "") if not m: @@ -75,20 +81,32 @@ rstatus, rconclusion = run.get("status"), run.get("conclusion") if rstatus != "completed": undetermined("run %s status=%s(runner 需开机在线;队列与日志见 %s/actions)" % (run_id, rstatus, web)) +try: + jobs = get(api + "/actions/runs/%s/jobs" % run_id).get("jobs", []) or [] +except Exception as e: + unknown("查 run %s 的 job 明细失败:%s——取不到步级读数就不下绿" % (run_id, e)) + +gate_steps = [x.get("conclusion") for j in jobs for x in (j.get("steps") or []) if "跑闸" in (x.get("name") or "")] + if rconclusion == "success": - print("CI现查: 绿 | commit %s | run %s conclusion=success" % (short(base), run_id)) + if not jobs: + unknown("run %s conclusion=success 但 job 列表为空,无法确认闸是否真跑" % run_id) + if not gate_steps: + unknown("run %s 里没有「跑闸」步——工作流结构已改,本件的步级判据需随动(查 %s/actions/runs/%s)" % (run_id, web, run_id)) + if all(c == "skipped" for c in gate_steps): + print("CI现查: 未验 | commit %s | run %s conclusion=success 但跑闸步全为 skipped——零触发批,远端一条闸都没跑" % (short(base), run_id)) + sys.exit(4) + print("CI现查: 绿 | commit %s | run %s conclusion=success,跑闸步 %d 条真跑过(闸况明细查 job 日志)" % (short(base), run_id, len(gate_steps))) sys.exit(0) red_jobs = [] -try: - jobs = get(api + "/actions/runs/%s/jobs" % run_id) - for j in jobs.get("jobs", []): - if j.get("conclusion") in ("success", "skipped", None): - continue - bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")] - red_jobs.append((j.get("name"), bad_steps)) -except Exception as e: - red_jobs.append(("" % e, [])) +for j in jobs: + if j.get("conclusion") in ("success", "skipped", None): + continue + bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")] + red_jobs.append((j.get("name"), bad_steps)) +if not red_jobs: + red_jobs.append(("" % rconclusion, [])) print("CI现查: 红 | commit %s | run %s conclusion=%s" % (short(base), run_id, rconclusion)) for name, steps in red_jobs: From 9c14f23ab8fc010a372e30280ee792d6cdc62564 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 06:49:01 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E6=94=B6=E5=B0=BE(=E8=BF=AD=E4=BB=A351?= =?UTF-8?q?=E6=89=B9=E5=90=8E=E6=94=B6=E5=B0=BE):=20=E5=8F=8D=E5=93=BA?= =?UTF-8?q?=E7=82=B9=E5=8D=81=E6=9D=A1=E8=90=BD=E6=96=87=20architecture.md?= =?UTF-8?q?(RoundCycle/BattleRefreshFlow=E6=9D=9F=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3/settlement=E6=AE=B5=E7=B1=BB=E6=9D=9F?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E5=AF=B9=E7=BC=96=E6=8E=92=E5=99=A8=E9=9B=B6?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E4=B8=8E=E5=9B=9Bhelper=E5=BD=92=E4=BD=8D/da?= =?UTF-8?q?mage=C2=B7RewardGrant=C2=B7BattleFxHost=C2=B7breakpoint=5Fstore?= =?UTF-8?q?=C2=B7save=5Fmanager=C2=B7slot=5Fstore=E5=85=AC=E5=BC=80?= =?UTF-8?q?=E9=9D=A2=E6=8D=A2=E4=BB=A3/minesweeper=5Frules=E5=BD=92?= =?UTF-8?q?=E4=BD=8D=E4=B8=A4=E6=B3=95=E4=B8=8E=E8=B6=85=E9=98=88=E5=80=BC?= =?UTF-8?q?=E8=BF=98=E5=80=BA=E5=9C=A8=E5=86=8C/charge=5Flayers=E9=80=80?= =?UTF-8?q?=E5=BD=B9=E4=B8=A4=E5=A4=84=E9=97=AD=E9=9D=A2=E5=9B=9B=E9=94=AE?= =?UTF-8?q?;=20=E7=AC=AC=E5=8D=81=E4=B8=80=E6=9D=A1=E8=90=BD=E7=82=B9?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E8=B7=B3=E8=BF=87=E5=9C=A8=E6=A1=88?= =?UTF-8?q?);=20S13=E6=97=A0=E5=87=AD=E8=AF=81=E5=9F=BA=E7=BA=BF=E9=94=AE?= =?UTF-8?q?=E7=BB=8F=E7=94=A8=E6=88=B7=E8=A3=81=E6=92=A4=E9=94=80(gdstyle?= =?UTF-8?q?=E7=BA=A2=E5=9C=A8=E6=A1=88=3D=E9=A2=84=E6=9C=9F=E5=8D=95?= =?UTF-8?q?=E9=94=AE,=E8=BF=98=E5=80=BA=E5=BD=92P0)+gdstyle=5Fgate=20--ref?= =?UTF-8?q?resh=E9=87=8D=E7=94=9F=E6=88=90=E5=85=A5=E5=8F=A3=E5=88=A0?= =?UTF-8?q?=E9=99=A4(=E5=9F=BA=E7=BA=BF=E7=BB=B4=E6=8A=A4=E6=94=B9?= =?UTF-8?q?=E4=BA=BA=E5=B7=A5=E4=BF=AE=E8=AE=A2,--refresh=E4=BC=A0?= =?UTF-8?q?=E5=8F=82exit=3D2)+gdstyle.toml=E4=B8=8E=E5=9F=BA=E7=BA=BF?= =?UTF-8?q?=E5=A4=B4=E6=B3=A8=E5=90=8C=E6=AD=A5;=20=E5=BE=85=E5=8A=9E?= =?UTF-8?q?=E6=B8=85=E5=8D=95:=20minesweeper=5Frules=E5=85=AC=E5=BC=80?= =?UTF-8?q?=E9=9D=A2=E8=BF=98=E5=80=BAP0+EffectEngine=E6=AD=BB=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E4=B8=8E=E5=85=A8=E4=BB=93=E5=AD=A4=E5=84=BF=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=B8=85=E9=80=80P0=E4=B8=A4=E6=9D=A1=E7=99=BB?= =?UTF-8?q?=E8=AE=B0/presentation=E5=8D=A1=E6=9C=AA=E7=BC=96=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=E8=BF=81=E5=87=BA=E6=89=BF=E6=8E=A5=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?/iter51=E4=B8=89=E6=9D=A1=E7=9B=AE(L1=E6=B7=B7=E7=BC=96=C2=B7?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=AE=A1=E6=9F=A5=E4=BD=99=E9=A1=B9=C2=B7Eff?= =?UTF-8?q?ectPort=E5=88=A0=E9=94=AE)=E5=AE=8C=E6=88=90=E6=95=B4=E6=9D=A1?= =?UTF-8?q?=E5=88=A0=E9=99=A4/=E4=B8=A4=E6=9D=A1P0=E5=AE=9E=E5=86=B5?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E6=94=B9=E5=86=99;=20roadmap=E8=BF=AD?= =?UTF-8?q?=E4=BB=A351=E8=A1=8C=E5=B7=B2=E5=AE=8C=E6=88=90(2026-11-04)+?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E4=BD=8D=E6=8D=A2=E4=BB=A3;=20=E4=BA=A7?= =?UTF-8?q?=E5=87=BA=E7=89=A9=E9=80=80=E5=BD=B9=E4=B8=89=E4=BB=B6(?= =?UTF-8?q?=E5=8F=8D=E5=93=BA=E7=82=B9+=E5=AE=9E=E7=8E=B0=E6=8A=A5?= =?UTF-8?q?=E5=91=8A+=E9=AA=8C=E6=94=B6=E5=AE=A1=E8=AE=A1=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=90=B8=E6=94=B6=E5=8D=B3=E6=AD=BB+=E8=AF=81?= =?UTF-8?q?=E6=8D=AE=E5=8F=AA=E7=95=99=E6=9C=80=E6=96=B0,=E7=9B=AE?= =?UTF-8?q?=E6=A3=80=E4=B9=89=E5=8A=A1=E2=91=B1=E5=9C=A8=E5=86=8C=E6=89=BF?= =?UTF-8?q?=E8=BD=BD,=E5=BC=95=E7=94=A8=E9=9D=A2=E5=B7=B2=E6=94=B9?= =?UTF-8?q?=E5=86=99);=20=E9=AA=8C=E8=AF=81:=20gdstyle=E5=8D=95=E9=94=AE?= =?UTF-8?q?=E7=BA=A2=3D=E9=A2=84=E6=9C=9F,--refresh=20exit=3D2,--docs?= =?UTF-8?q?=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engineering/architecture/architecture.md | 24 +-- reports/iter51-反哺点-2026-11-04.md | 29 --- reports/iter51-实现报告-2026-11-04.md | 102 ---------- reports/iter51-验收审计-2026-11-04.md | 190 ------------------ roadmap.md | 6 +- tools/gdstyle.toml | 2 +- tools/gdstyle_baseline.txt | 5 +- tools/gdstyle_gate.py | 70 +------ 待办清单.md | 50 +++-- 9 files changed, 50 insertions(+), 428 deletions(-) delete mode 100644 reports/iter51-反哺点-2026-11-04.md delete mode 100644 reports/iter51-实现报告-2026-11-04.md delete mode 100644 reports/iter51-验收审计-2026-11-04.md diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index 087faa46..cbc0a6ce 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -86,7 +86,7 @@ | 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API | |---|---|---|---|---| | **ConfigDB**(autoload,D2) | 全部玩法数值表与表现层布局配置表(`data/tables/*.tres` + `data/schemas/*.gd`);加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进四张域容器 Resource(`config_domain_combat/run/crossrun/presentation.gd`——前三件迭代 41 已建件,第四件表现层布局域 2026-10-30 建件,类型化成员 7/7/5/1);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 10 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW | -| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10/类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW | +| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10→11——+active_path 读投影、migrate_legacy_save 公开化;对 SaveManagerCore 反向私取 21 处清零=双向公开面;类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW | | **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,已建件——迭代 41 存量债批) | 存档内元进度数据的读写唯一层(自 save_manager 收编 accessor 族:钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 写口原语 13+只读投影 6(tree_state/forge_state/attune_state/career_state 四投影+keystone_balance/item_unlocked)=公开 19(add_keystone 私有 `_add_keystone`),零 _data 副本零缓存;MetaProgression 业务层与营地/锻造 UI 消费方经 `save.meta_state.` 直连(meta 的纯转发读方法已退役) | save_manager(序列化接线);FileAccess——LOW | | **MetaProgression**(autoload,`core/meta_progression.gd`,D3 元进度业务层) | 元进度账本服务:钥石收束入账、营地消费定价与秘宝解锁判定的纯被动服务,零信号零外发;数据读写全经注入的 MetaStateStore(`save.meta_state.` 直连);hero_override() 现算不写表(全零=白板合法态——生效注入只走冒险开始参数面显式传参) | 写口族(天赋树购买/叉侧切换/锻造解锁与升级与选中/同调装配切换等)+解锁判定与读投影;**构造双形态**=autoload 装配走 `.new()` 零参+`_ready` 从 autoload 链取依赖(工程序 ConfigDB→RngService→SaveManager→MetaProgression,注册顺序即依赖序),单测/工具走 `.new(db, save)` 直构形态(不入树零 _ready)——**四个 Autoload 中唯一有跨 Autoload 依赖的件(ConfigDB/SaveManager)** | ConfigDB(定价与档位表值);SaveManager(存档访问器,经节点路径延迟取用) | autoload 生命周期——LOW | | **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 | @@ -94,7 +94,7 @@ | **variation 定义**(A4) | VariationTable schema(现行 6 行:六 region——贫矿脉 global 行 2026-10-16 用户令屏蔽删行,global 型机制代码保留)与 F1 候选池门控谓词 | `variant_pool(level_index, board_type, node_type="")`(门控三维:unlock+board_types+节点身份——global 行仅 boss/elite 入池,缺省跳过零扰动);`global_variant_row(level_index, board_type, node_type)`(全局型单行决议访问器) | ConfigDB(VariationTable) | 无 | | **status_container**(B3) | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 5 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB(StatusEffectTable) | 无(RefCounted) | | **effect_engine**(内容数据驱动扩展批 iter17 建域) | 零运行时可变状态的系统(见「架构原则」条 8):全同步解释器——事件由宿主在明确代码点位显式调 dispatch 进件,命中序=装载声明序(feats→treasure→树规则)写死保种子复放,不是事件总线/事件循环;事件/事实/效果类型/算子四词表封闭 + 树规则合成订阅六键(posture 每关首轮窗/sense 两组限次 counter 闸·adv_ 前缀走冒险桶跨战斗持久) | rebuild(每战重建订阅表)/`dispatch(ctx, event, payload, facts)`/`validate_trigger_row` + 静态计数口 `counter`·`counter_add`·`counter_spend`·`bucket_of`·`counter_id_legal`(计数器读写只经 EffectContext 引用束,无导出/灌入接口) | ConfigDB(feats/treasure 行集)、EffectContext(四桶引用由宿主持有:round/battle 桶归编排器、level 桶归关卡域、adventure 桶归应用层——RunLedger 已随迭代 39 拆件批退役,桶字典由 adventure_runner 持有) | 无(零随机全确定) | -| **save_system**(D3,Alpha;iter27 已升格 autoload「SaveManager」) | 存档格式与版本比较 | (契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SAVE_FORMAT_VERSION + master_seed + 各流 scope 计数器快照,须在无正在执行中的 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW | +| **save_system**(D3,Alpha;iter27 已升格 autoload「SaveManager」) | 存档格式与版本比较 | save_manager 公开面 7→14(+set_read_only/read_only/reset_to_default/data_snapshot/restore_data 五承接方法+peek_summary_at/rename_overwrite 两更名公开化);(契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SAVE_FORMAT_VERSION + master_seed + 各流 scope 计数器快照,须在无正在执行中的 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW | | **settings/localization**(D6,愿景) | 设置项、翻译数据 | (契约规范源:`../../gdd/systems/表现与支撑/设置与本地化.md`) | ConfigDB | 无 | ### 核心层 @@ -102,7 +102,7 @@ | 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API | |---|---|---|---|---| | **board_generator**(A1) | 生成算法(五阶段管线)、BoardGenTable 回填、生成期指标集、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量(metric_version);生成姿态 = MVP 同步生成 + 加载幕(切片实证 10×10/12×12 同步可接受,WorkerThread 留扩展) | `generate(L, D, rng) -> Board`;度量独立入口×3(推理器 / F3 度量 / 合法性谓词——独立纯函数、同一实现) | ConfigDB(BoardGenTable/VariationTable);board_gen 流(一次 open_stream = 一张图);dig_ap_cost 只读 | 无 | -| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 封印集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);查询只读面(current_board / hero_pos / is_revealed / has_seal 等);`compute_move_path(cell)` 只读;`preview_reveal(cell)` 只读预览口(零写入零事件——0 格连锁传播闭包;迭代 50 己案建口);`retire_board()`;三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举 | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end(迭代 41 由属性改方法)/ 相位 | 无(零表读取、零 UI——切片实证) | +| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 封印集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);查询只读面(current_board / hero_pos / is_revealed / has_seal 等);`compute_move_path(cell)` 只读;`preview_reveal(cell)` 只读预览口(零写入零事件——0 格连锁传播闭包;迭代 50 己案建口);`retire_board()`;+harmful_variants/harmful_adjacent 两有害判定面(自编排器归位);三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举;公开方法面 19→21(超阈值贴顶——还债归待办清单 P0 条目「minesweeper_rules 公开面还债」) | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end(迭代 41 由属性改方法)/ 相位 | 无(零表读取、零 UI——切片实证) | | **unit_container**(B2) | hero/enemy 单位实例(hp/atk/实例池:池快照+冷却+摇定动作+保底计数)、行值快照 | spawn / query_alive / rollable_skills / special_skills / check_trigger_unlocks / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / check_skill_unlocks / despawn_all(2026-09-16 敌人技能池批:query_beat/query_attack/advance 随行为环退役;rollable_skills/special_skills/check_trigger_unlocks 带 facts 参——2026-10-27 情境条件触发器实装,事实缺键=条件假拦下,百分比事实走阈值式折算) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 | | **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影;装配期摇号面已拆出掷格件 RewardRoller(2026-11-02 轴戊设计闭合、迭代 49 卡① 建件——roll_board 装配序=守卫→复位→委托→收纳→ready;受理账面留守) | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / phase(weighted_pick 退役——返行选行算子迁掷格件);另有 is_mapped 静默布尔投影(批量工具用);实测类变量 11 | ConfigDB(RewardTable);BattleFxHost 经注入 Callable `luck_bias_query` 取偏运合计(reward_roll 发射与 bias_rewards 求和移至效果层完成);reward_roll 流(scope = 每次 install 批,取号经掷格件) | 无 | | **helper_items**(A5,切片已实装秘宝域) | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 | @@ -114,21 +114,21 @@ | **RefreshBank**(`core/refresh_bank.gd`,刷新账件,已实装(2026-10-27 迭代 39 拆件批)) | 三选一刷新三源账唯一所有者:X 刷新件每碰三选一重给(优先消耗不结转)/Y 营地升级行冒险周期池(用完即无)/刷新券存量账(冒险级跨停点持久)+GP 付费刷计数与报价公式;零停点态依赖(免费/付费支出的编排归 CurioStop,本件只记账) | 支出与余量原语+报价公式;断点自交片段(refresh_stock/attune_refresh_y 两键,Y 缺键回退参数面) | ConfigDB(刷新件与营地升级行表值);注入 CurioStop 支出路径 | 无(RefCounted 纯逻辑) | | **AttuneLoadout**(`core/attune_loadout.gd`,同调配装件,已实装(2026-10-27 迭代 39 拆件批)) | 装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期);同调充能的物化行为唯一事实源——种行/补行改写时同步(RunLedger 充能字典轨与差额对账函数退役);旧档 aux_items 同调行迁移归件 | 构造注入;充能查询/消耗/按周期重置原语;断点自交片段(attunement_charges 键,键名与策略不变) | ConfigDB(秘宝行集);TreasureItems(同调行投放面);注入战斗使用路径 | 无 | | **CurioStop**(`core/curio_stop.gd`,三选一停点件,已实装(2026-10-27 迭代 39 拆件批)) | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途)、刷新编排(三源账面归 RefreshBank 件、查停点态→支出→重抽重发编排留本件)、兑付方法族(pick/refresh/redeem/skip)、凭证出箱(RAP/时沙待注入);三选一信号面自带;零自动推进停点语义承载 | 对外=开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented/curio_resolved);由 AdventureRunner 关卡完成受理时调用 | ConfigDB;注入 CurioDeck(抽签/摘牌)、HeroStats 与 GainJournal(兑付落账单写原语)、RefreshBank(刷新支出);随机只经 CurioDeck 的 reward_pick 封闭流 | 无 | -| **breakpoint_store**(`core/breakpoint_store.gd`,冒险域·断点组装与回放件,已建件——迭代 45) | 存档断点组装与回放唯一承载(自 adventure_runner 收编 6 成员+函数族):断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入(九引用束成员解包为原名成员——保迁移函数体逐字);`resume_adventure` 留宿主(壳+宿主侧回填语句——R2 所有权缝分家);断点键面零变化;实测类变量 15(贴顶禁再增) | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) | +| **breakpoint_store**(`core/breakpoint_store.gd`,冒险域·断点组装与回放件,已建件——迭代 45) | 存档断点组装与回放唯一承载(自 adventure_runner 收编 6 成员+函数族):断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入(九引用束成员解包为原名成员——保迁移函数体逐字);`resume_adventure` 留宿主(壳+宿主侧回填语句——R2 所有权缝分家);断点键面零变化;公开方法面 5→18(13 新面承接 adventure_runner 私取清零+bp_read 公开化);实测类变量 15(贴顶禁再增) | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) | ### 功能层 | 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API | |---|---|---|---|---| | **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层 + 字段分层,词表以 owner「明细流水」节为准)、条款③⑤闭合对账、**战斗域全部跨系统调用权**、效果引擎宿主面与树规则挂点施加面(**已拆出 BattleFxHost 并实装(2026-10-28 迭代 40——battle_fx_host.gd/reward_grant.gd 两件在库,效果引擎宿主面归 BattleFxHost 行承载,拆件细则见「战斗编排域拆件定稿」节);2026-10-29 零豁免裁设计闭合:RoundCycle/BattleFacts 两新件随批(宿主 38→15、S13 豁免第三条注销——见下两行与定稿节轴丁)**) | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方=B1 域文件集:battle_orchestrator.gd + settlement/ 五段段类 + damage_controller + 拆件后三新件**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) | -| **BattleFxHost**(`core/battle_fx_host.gd`,B1 域·效果引擎宿主件,2026-10-26 编排域拆件设计定稿——已建件(迭代 40 实装)) | 效果引擎挂点施加面唯一承载(自 battle_orchestrator 收编 fx 族):事件发射与事实判定(_fx_emit/_fx_fact)、主角 AC 基线与本轮/本战 AC 累积、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含 _fx_revealing 再入闸——同步信号回环防二次缓冲二次兑付)、树规则合成订阅的 round/battle 桶计数器持有(桶随编排器生灭语义不变);2026-11-02 轴戊设计闭合落地(迭代 49 卡①):效果端口件为业务类朝向机房的窄门面——端口在本件组建、bind_port 组装期转注两业务类,计数器账面收归本件;实测类变量 15 | 构造=引用束注入(防 _init max-parameters 新键);bind 时注入 emit Callable(行发射经宿主协议面单点)与兑付 Callable(RewardGrant 授予面——揭示队列 flush 回调);只读投影分两类消费方:AC 累积/bonus_delta 供宿主呈现快照与战末 drain,揭示态供宿主 A2 订阅旁路判定;`luck_bias_for(cell_count) -> int` 偏运合计查询口(体内三参形 dispatch——reward_roll 发射与 bias_rewards 求和自 RewardEconomy 收回本件,RewardEconomy 经注入 Callable 消费) | EffectEngine(EffectContext 引用束);RngServiceCore(fx_reveal 封闭流——除该流外零随机);A2 minesweeper(板读与 fx_reveal 调用);A3 action_economy(queue_ap_delta/charge_layers 读);B3 status_container 读;B2 unit_container 读(hp_ratio);DamageController(set_hero_ac_override);ConfigDB;encounter 引用 | 无(零 await——效果引擎全同步解释器) | -| **RewardGrant**(`core/reward_grant.gd`,B1 域·奖励授予件,2026-10-26 编排域拆件设计定稿——实现批建件) | 奖励授予唯一兑付管线(自 battle_orchestrator 收编 grant 族):极性字典路由与封印溢价兑付(_grant_routed)、三种行应用(flat/percent/gp)、伤害单据提交面(_submit 族)、清图奖励与身份授予、秘宝使用编排(use_treasure 四步)、战斗域 GP 缓冲 escrow 与授予段暂存伤害闸(_defer_board_damage/_deferred_board) | use_treasure 编排入口(treasure_interact_controller 经组合根现值回调直连);授予入口(结算段类直调);只读投影(escrow 读与 GP 掉落合计——宿主呈现快照与 _roll_gp_drop 消费) | B4 RewardEconomy(polarity_row/grant);B1 域 DamageController(开单);BattleFxHost 引用(treasure_used 事件发射与计数器消耗);A2 minesweeper(校验回调/fx_reveal/remove_mines_in);A3 action_economy(相位闸/grant_ap/queue_ap_delta);B3 status_container(apply_effect/池读);B2 unit_container(hero_unit/heal);ConfigDB;注入 emit Callable、宿主 fatal Callable(battle_result 状态机唯一写点留宿主)、宿主 attune 读投影(_attune_safe_seen)、encounter 账本引用;TreasureItems 经宿主 setter 转发注入(库存注入时序在宿主构造之后) | 无(零随机零 await——授予期零随机口径,roll 值授予前已定) | -| **damage_controller**(B1 域·伤害单据管线) | 伤害单据唯一兑现管线(单据九栏目声明→分量掷点/克制修正/伤害下限归一→明细行发射;双方同规);`damage_taken` 唯一发射口(_emit_hero_damage_taken)+唯一消费族(_apply_hero_damage_effects)——敌方攻击/地形/危险格/错误封印直伤四路径统一发射,board_flat_hero 结账前统一发射 | 兑付入口(编排侧开单)+ fx 揭示回调注入位;实测类变量 12 | B2 单位实例(单据目标传入);B1 注入(EffectPort 端口引用——轴戊设计闭合改经端口发射,迭代 49 卡① 建件;EffectContext 引用束与编排回调收归宿主侧 BattleFxHost 域,业务类零引擎直连) | 无 | -| **BattleRefreshFlow**(`core/battle_refresh_flow.gd`,B1 域·换图刷新流程件,已建件——迭代 46) | 弃图刷新流程唯一承载(自编排器收编):刷新可用性门(相位/每轮上限/储备门槛)、折后价单点(_effective_refresh_cost——专长行声明序第一命中、floor 取整、can 与执行同源)、换图执行序(扣储备→used+1→旧资源退役→旧板退役→装新板→board_refreshed 成功事实登记) | can_refresh/execute 两公开入口;调用路径呈现层→编排器两公开方法单行转发→本件;A3 request_refresh 收折后价入参 | 编排器(宿主,B1 域内唯一调用方约束同族适用);A3 action_economy(折价扣费与拒绝判据);A2 装板壳(装板与退役);RewardGrant(apply_board_refreshed 成功事实消费);feats 折价行经 ConfigDB;static_scan S7_B1_SET 已增补本件(判据口径不变) | 无(零随机零 await) | +| **BattleFxHost**(`core/battle_fx_host.gd`,B1 域·效果引擎宿主件,2026-10-26 编排域拆件设计定稿——已建件(迭代 40 实装)) | 效果引擎挂点施加面唯一承载(自 battle_orchestrator 收编 fx 族):事件发射与事实判定(_fx_emit/_fx_fact)、主角 AC 基线与本轮/本战 AC 累积、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含 _fx_revealing 再入闸——同步信号回环防二次缓冲二次兑付)、树规则合成订阅的 round/battle 桶计数器持有(桶随编排器生灭语义不变);2026-11-02 轴戊设计闭合落地(迭代 49 卡①):效果端口件为业务类朝向机房的窄门面——端口在本件组建、bind_port 组装期转注两业务类,计数器账面收归本件;实测类变量 15 | 构造=引用束注入(防 _init max-parameters 新键);bind 时注入 emit Callable(行发射经宿主协议面单点)与兑付 Callable(RewardGrant 授予面——揭示队列 flush 回调);+mark_new_resource_type/+add_bonus_delta 两公开累积方法(grant 消费面——类变量 15 不变,方法-only);只读投影分两类消费方:AC 累积/bonus_delta 供宿主呈现快照与战末 drain,揭示态供宿主 A2 订阅旁路判定;`luck_bias_for(cell_count) -> int` 偏运合计查询口(体内三参形 dispatch——reward_roll 发射与 bias_rewards 求和自 RewardEconomy 收回本件,RewardEconomy 经注入 Callable 消费) | EffectEngine(EffectContext 引用束);RngServiceCore(fx_reveal 封闭流——除该流外零随机);A2 minesweeper(板读与 fx_reveal 调用);A3 action_economy(queue_ap_delta 读);B3 status_container 读;B2 unit_container 读(hp_ratio);DamageController(set_hero_ac_override);ConfigDB;encounter 引用 | 无(零 await——效果引擎全同步解释器) | +| **RewardGrant**(`core/reward_grant.gd`,B1 域·奖励授予件,2026-10-26 编排域拆件设计定稿——实现批建件) | 奖励授予唯一兑付管线(自 battle_orchestrator 收编 grant 族):极性字典路由与封印溢价兑付(_grant_routed)、三种行应用(flat/percent/gp)、伤害单据提交面(_submit 族)、清图奖励与身份授予、秘宝使用编排(use_treasure 四步)、战斗域 GP 缓冲 escrow 与授予段暂存伤害闸(_defer_board_damage/_deferred_board) | use_treasure 编排入口(treasure_interact_controller 经组合根现值回调直连);授予入口(结算段类直调);+feat_yield_adjust(专长收益调整——自编排器归位);只读投影(escrow 读与 GP 掉落合计——宿主呈现快照与 _roll_gp_drop 消费) | B4 RewardEconomy(polarity_row/grant);B1 域 DamageController(开单);BattleFxHost 引用(treasure_used 事件发射与计数器消耗);A2 minesweeper(校验回调/fx_reveal/remove_mines_in);A3 action_economy(相位闸/grant_ap/queue_ap_delta);B3 status_container(apply_effect/池读);B2 unit_container(hero_unit/heal);ConfigDB;注入 emit Callable、宿主 fatal Callable(battle_result 状态机唯一写点留宿主)、宿主 attune 读投影(_attune_safe_seen)、encounter 账本引用;TreasureItems 经宿主 setter 转发注入(库存注入时序在宿主构造之后) | 无(零随机零 await——授予期零随机口径,roll 值授予前已定) | +| **damage_controller**(B1 域·伤害单据管线) | 伤害单据唯一兑现管线(单据九栏目声明→分量掷点/克制修正/伤害下限归一→明细行发射;双方同规);`damage_taken` 唯一发射口(_emit_hero_damage_taken)+唯一消费族(_apply_hero_damage_effects)——敌方攻击/地形/危险格/错误封印直伤四路径统一发射,board_flat_hero 结账前统一发射 | 兑付入口(编排侧开单)+ fx 揭示回调注入位;+hero_atk()/static hits_of()/emit_hero_damage_taken 公开化(_emit 去下划线非并存——编排器与段类消费);实测类变量 12→13(+facts 构造注入) | B2 单位实例(单据目标传入);B1 注入(EffectPort 端口引用——轴戊设计闭合改经端口发射,迭代 49 卡① 建件;EffectContext 引用束与编排回调收归宿主侧 BattleFxHost 域,业务类零引擎直连) | 无 | +| **BattleRefreshFlow**(`core/battle_refresh_flow.gd`,B1 域·换图刷新流程件,已建件——迭代 46) | 弃图刷新流程唯一承载(自编排器收编):刷新可用性门(相位/每轮上限/储备门槛)、折后价单点(_effective_refresh_cost——专长行声明序第一命中、floor 取整、can 与执行同源)、换图执行序(扣储备→used+1→旧资源退役→旧板退役→装新板→board_refreshed 成功事实登记) | can_refresh/execute 两公开入口(构造=实例束注入 8 键——facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg;类变量 0→8);调用路径呈现层→编排器两公开方法单行转发→RoundCycle.refresh_flow() 取件→本件;grant_seg 段间引用直接交接(编排器 _rounds._grant_seg 跨两层链式取段消灭);A3 request_refresh 收折后价入参 | 编排器(宿主,B1 域内唯一调用方约束同族适用);A3 action_economy(折价扣费与拒绝判据);A2 装板壳(装板与退役);RewardGrant(apply_board_refreshed 成功事实消费);feats 折价行经 ConfigDB;static_scan S7_B1_SET 已增补本件(判据口径不变) | 无(零随机零 await) | | **BoardInstaller**(`core/board_installer.gd`,B1 域·装板域件,2026-10-29 尾波清偿批设计闭合——实现批建件,细则见「战斗编排域拆件定稿」节轴丙) | 棋盘安装与变异布置域唯一承载(自 battle_orchestrator 收编装板族六函数):生成受理、全局作用域变异决议、资源摇号资格域组装(roll_board 恰一次)、region 变异决议与连通片布置;BoardGenerator 实例随件自建自接线(宿主成员退役)——宿主侧装板后状态复位(封印/完美计数清零、板序推进、board_changed 发射、秘宝叠加层退役)留宿主薄壳,时序逐字保持 | `install(during_round) -> bool` 单入口;构造=引用束注入(rng/db/minesweeper/reward_economy/encounter 只读引用+宿主 fatal Callable——防 _init max-parameters 新键) | A1 board_generator(件内持有);A2 install_board;B4 roll_board/board_skew 写;A4 变异决议行集经 ConfigDB;board_gen/variant_place 两封闭流随件、流名与开流时点零漂移 | 无(零 await——装板全同步) | -| **RoundCycle**(`core/round_cycle.gd`,B1 域·轮循环驱动件,已建件——迭代 44) | 轮循环驱动域唯一承载(自宿主收编 7 成员):结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 三参直构(host/fx_host_ref/reward_grant_ref——五段构造透传不落成员);段驱动入口由宿主接线调用;实测类变量 8/公开方法 3 | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 | +| **RoundCycle**(`core/round_cycle.gd`,B1 域·轮循环驱动件,已建件——迭代 44) | 轮循环驱动域唯一承载(自宿主收编 7 成员):结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 引用束注入(编排器 20 键封闭束,解包自用七键后原束分发五段;持有 BattleRefreshFlow);段驱动入口由宿主接线调用;公开方法 3→4(refresh_flow() 读出口);实测类变量 8→15(贴顶禁再增) | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 | | **BattleFacts**(`core/battle_facts.gd`,B1 域·战斗事实件,已建件——迭代 44) | 战斗事实域唯一承载(自宿主收编 14 成员):受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 零参直构;宿主与段类经注入引用读写;实测类变量 15(贴顶)/公开方法 4(reset_round+三 mark 写口) | B1 宿主;A2/A3 只读 | 无 | -| **settlement/ 五段段类**(B1 域·结算段件) | B1 每轮结算序列的五段逻辑件(授予/增益减益/战斗/衰减/判定——细则见下「战斗域装配现状」节) | 段处理入口(B1 接线调用);可直接 `new()` 单测 | B2/B3/B4 兑付面与 B1 轮 facts 缓冲(各段按职责) | 无 | +| **settlement/ 五段段类**(B1 域·结算段件) | B1 每轮结算序列的五段逻辑件(授予/增益减益/战斗/衰减/判定——细则见下「战斗域装配现状」节) | 段处理入口(B1 接线调用);构造=引用束注入(各段断言各自必需键);段类对编排器零引用(注入 Callable 亦属接线面);可直接 `new()` 单测 | B2/B3/B4 兑付面与 B1 轮 facts 缓冲(各段按职责) | 无 | | **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1(直构 BattleOrchestrator 接线战斗)、ConfigDB LevelTable | 无 | | **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 | 无 | | **difficulty**(C3) | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表(切片 monte_carlo 为工具原型) | 调参输出(经 D2 R1 解析/编排器传递,禁反向调用生成) | ConfigDB、B1 对账输出(调参约束清单)、A1 报告 C | 无 | @@ -143,10 +143,10 @@ - **轴甲两件**:battle_orchestrator(拆件时 S13 全库单文件最大户——行数与成员数为历史读数,冻结计数不登记,查 git)按变化原因拆出 BattleFxHost(效果引擎宿主域)与 RewardGrant(奖励授予域)两协作件;宿主保留架构在册职责全部——战斗状态机与生命周期、轮 facts 缓冲(_facts_reveals/seals/path)、A2 三事件订阅、结算五段驱动、attune 族原位、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(presentation/diagnostic_snapshot 唯一出口不变)、刷新面、nest_preview_count(巢穴预告读数口——外部直读面 main.gd 与 treasure_effects_test 随批点名冻结)。拆件动机与残留债口径见待办清单「gdstyle 存量债全量清偿」条目。 - **轴丙一件(2026-10-29 尾波清偿批设计闭合——实现批建件)**:battle_orchestrator 装板域六函数(`_install_new_board`/`_resolve_global_variant`/`_place_variants`/`_place_variants_apply`/`_variants_fringe`/`_adjacent_to_packs`,约 140 行;全仓仅宿主三调用点——begin_battle/中途刷新受理/player_request_refresh,实查零外部调用)按变化原因拆出 BoardInstaller,职责语句「棋盘安装与变异布置域」(生成→全局变异决议→装板受理→资源摇号→变异布置;单一变化原因=棋盘设置顺序与变异布置算法)。形态两修正(实测裁,与初案「逐字搬运」之差):①宿主复位块不随件——`_install_new_board` 体内五处计数清零、板序推进、board_changed 发射与秘宝叠加层退役留宿主薄壳 `_install_and_reset`,三调用点改调薄壳、时序逐字;②`_board_generator` 全文件仅装板族使用——实例随件自建自接线(fatal 经注入 Callable),宿主成员退役(类变量 38→37,禁无效成员义务)。encounter(23 处跨宿主)与 _reward_economy(18 处)确认留宿主、经引用束注入。文件长键(1035/1000)随本件迁出归绿;attune 族与 begin_battle 受理段序禁动不变。 - **轴丁两件(2026-10-29 零豁免裁设计闭合——实现批建件)**:RoundCycle(轮循环驱动件)与 BattleFacts(战斗事实件)见功能层表两行——宿主 38(轴丙落地后 37)→15,S13 豁免第三条注销;宿主保留面改登记为「战斗状态机与生命周期、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(两快照出口签名不变)、刷新面、nest_preview_count」;attune 族处置由下一条改判;treasure_inventory 注入改址追认——原「外部赋值路径零改址」句失效,level_runner 经 RewardGrant 出口直注;成员级对账以需求卡片为卡内契约(开工 T0 复核)。 -- **轴戊两件(2026-11-02 用户裁甲案设计闭合——已建件,迭代 49 卡① 合回,git 提交 848723b5)**:①**效果端口件 EffectPort**(`core/effect_port.gd`)——职责语句「战斗业务类唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批」;实测形态=六 Callable 路由、bind_port 组装期转注、闭面五键 biz_fact、未绑零扰动缺省;`DamageController`/`ActionEconomy` 各持端口引用(同一实例、构造注入),`EffectEngine`/`EffectContext` 引用与计数器账面(value_counter 层值读后清零)收归宿主侧(BattleFxHost 域),业务类零引擎直连、零账面触碰;facts 取值束(dig_streak/charge_layers/rider_tier_*)与入站回调束(reveal/ac)随发射契约归宿主侧注入面;端口**单实现零接口抽象**(无基类无多态——GDScript 无 interface,本案不需要);端口与宿主经注入 Callable 束协作、零宿主对象引用(跨件单向接线条款适用,禁双向强引用)。②**掷格件 RewardRoller**(`core/reward_roller.gd`)——职责语句「装配期格面资源摇号:池行装配、区间行 roll、权重合成(偏运硬币/board_skew 两路投影)」;实测形态=roll 批束四键(mapping/power_roll/clear_identity/clear_power)、选行算子 `_weighted_pick_row` 返表行、fatal 经构造 Callable 链;受理账面(grant/身份查询/快照/retire/polarity_row)留 `RewardEconomy`,衔接面=格→奖励身份映射(掷格件产出、受理件接收);掷格件经注入 RngServiceCore 取号(roll_board 流 scope=每次 install 批不变、取号序零漂移)。行为零变化红线沿本节「验收红线」全款:命中序=声明序、明细行协议词表零触碰、`_ineffective_row` 载荷 deferred 自描述键观测面不回退(2026-11-02 迭代 48 卡① 甲案在案)、EV/MC 锚同种子逐位零漂移——round_flow 逐位回放 134 检查为主证(红线成立);两新件 S13 预算随卡「实现规范预算」三查已过(新件全默认阈值内、gdstyle 基线外新违规 0),`RewardEconomy` 类变量随掷格面迁出 15→11;S7 B1 域文件集与 S8 await 文件集已随批增行。 +- **轴戊两件(2026-11-02 用户裁甲案设计闭合——已建件,迭代 49 卡① 合回,git 提交 848723b5)**:①**效果端口件 EffectPort**(`core/effect_port.gd`)——职责语句「战斗业务类唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批」;实测形态=六 Callable 路由、bind_port 组装期转注、闭面四键 biz_fact(dig_streak/hp_ratio/rider_tier_bonus/rider_tier_crit——charge_layers 键退役后宿主应答 null)、未绑零扰动缺省;`DamageController`/`ActionEconomy` 各持端口引用(同一实例、构造注入),`EffectEngine`/`EffectContext` 引用与计数器账面(value_counter 层值读后清零)收归宿主侧(BattleFxHost 域),业务类零引擎直连、零账面触碰;facts 取值束(dig_streak/rider_tier_*)与入站回调束(reveal/ac)随发射契约归宿主侧注入面;端口**单实现零接口抽象**(无基类无多态——GDScript 无 interface,本案不需要);端口与宿主经注入 Callable 束协作、零宿主对象引用(跨件单向接线条款适用,禁双向强引用)。②**掷格件 RewardRoller**(`core/reward_roller.gd`)——职责语句「装配期格面资源摇号:池行装配、区间行 roll、权重合成(偏运硬币/board_skew 两路投影)」;实测形态=roll 批束四键(mapping/power_roll/clear_identity/clear_power)、选行算子 `_weighted_pick_row` 返表行、fatal 经构造 Callable 链;受理账面(grant/身份查询/快照/retire/polarity_row)留 `RewardEconomy`,衔接面=格→奖励身份映射(掷格件产出、受理件接收);掷格件经注入 RngServiceCore 取号(roll_board 流 scope=每次 install 批不变、取号序零漂移)。行为零变化红线沿本节「验收红线」全款:命中序=声明序、明细行协议词表零触碰、`_ineffective_row` 载荷 deferred 自描述键观测面不回退(2026-11-02 迭代 48 卡① 甲案在案)、EV/MC 锚同种子逐位零漂移——round_flow 逐位回放 134 检查为主证(红线成立);两新件 S13 预算随卡「实现规范预算」三查已过(新件全默认阈值内、gdstyle 基线外新违规 0),`RewardEconomy` 类变量随掷格面迁出 15→11;S7 B1 域文件集与 S8 await 文件集已随批增行。 - **跨件单向接线**:RewardGrant 持 BattleFxHost 引用(treasure_used 事件发射与计数器消耗);BattleFxHost 对授予面走 bind 时注入的兑付 Callable(揭示队列 flush 回调)——禁双向强引用(两件均 RefCounted,环引用随每战新建泄漏)。行发射经 bind 时注入的 emit Callable(协议面单点留宿主——CurioDeck bind_handlers 同款先例,禁件回调宿主私有族);轮 facts 缓冲留宿主、fx 件经注入取值;_seal_consumed 留宿主(与 A2 seal_judged 订阅同域);fatal 经注入 Callable 转宿主 _fatal(battle_result 状态机唯一写点不变)。注入 Callable 亦属接线面、同单向纪律——禁为省参数改持对象引用。授予件对账本零强引用:战斗期消费经 `battle_hooks` 三键回调束(attune_consume/vision_add/negative_blast,bind_battle 缺键 assert),宿主侧三单点记账(2026-10-28 迭代 40 合并适配实装——RunLedger 类已随迭代 39 退役,直持对象引用无处可指)。 - **战斗域读面(RunLedger 退役后)**:账本读经编排器 `_journal()`/`_attune_loadout()` 现值派生(GainJournal/AttuneLoadout,enc 派生、无成员接收);冒险桶经 `enc["adv_bucket"]` 下传 `battle_fx_host.mount_buckets`;战末 bonus 四轴经 `GainJournal.gain_bonus` 汇入(2026-10-27 迭代 39 主角账三件落地、迭代 40 合并适配承接)。 -- **settlement/ 段类随动**:段类构造注入直持宿主+两新件三引用(Middle Man 禁令封转发壳——调用点改指),授予段读写面点名:_grant_routed×5/_submit×6/escrow 写/_fx_new_resource_types/_feat_first_dig_used/_fx_bonus_delta 写/_apply_ac_bonus/_fx_enqueue_reveal/_flush_pending_reveals/_grant_clear_reward/counters_ctx;战斗段 _defer_board_damage 复位与 _deferred_board 结账改指 RewardGrant。四个授予段 helper(_feat_yield_adjust/_harmful_variants/_harmful_adjacent/_hits_of)与 _pool_effective/perfect_sweep_earned 留宿主(段类经宿主引用直调,现状形态)。 +- **settlement/ 段类随动(2026-11-04 迭代51卡② 换代)**:段类构造=引用束注入(编排器 20 键封闭束分发——各段断言各自必需键),段类对编排器零引用(跨件单向接线,注入 Callable 亦属接线面)。四个授予段 helper 归位:_feat_yield_adjust→RewardGrant(feat_yield_adjust)、_harmful_variants/_harmful_adjacent→minesweeper_rules(harmful_variants/harmful_adjacent)、_hits_of→damage_controller(static hits_of);_pool_effective/perfect_sweep_earned 留宿主(段类经编排器公开读投影消费)。 - **attune 族处置(2026-10-29 零豁免裁改判,原「原位保留」失效)**:attune 状态两成员归 BattleFacts、行为函数留宿主——迭代 39 副卡(AttuneLoadout 充能单源化)语义领地不破,外部三面消费点零改址;RewardGrant 经宿主 attune 读投影取 _attune_safe_seen 形态不变。 - **呈现零变化**:两快照出口签名与载荷逐字不变,内部改读两件只读投影(快照本体不读揭示态——揭示态投影消费方=宿主 A2 订阅旁路判定);treasure_interact_controller 三触点分家——use_treasure 经组合根现值回调改注 RewardGrant(每战新建编排器,禁一次性直引防悬挂),minesweeper 读与 attune_convert_state 仍走宿主取值回调(双取值面);nest_preview_count 直读面零变化。 - **S13 键账收敛义务**:12 个超标键随函数迁入新文件=基线外 NEW(use_treasure 长度/分支/局部/返回×4、_grant_routed 参数 8、_dispatch_row 参数 6、_apply_percent_row 参数 6、_apply_gp_row 参数 6、_apply_flat_row 参数 7+返回 9、_submit_flat_hero 参数 7、_fx_fact 返回 7)——卡内同步收敛为本波义务(_grant_routed 等签名随引用束注入改形、use_treasure 体拆分),禁波 0 合并 refresh 吸收新文件未还键;新件 _init 引用束注入防 max-parameters 新键。 diff --git a/reports/iter51-反哺点-2026-11-04.md b/reports/iter51-反哺点-2026-11-04.md deleted file mode 100644 index a1da6912..00000000 --- a/reports/iter51-反哺点-2026-11-04.md +++ /dev/null @@ -1,29 +0,0 @@ -# 迭代 51 反哺点清单(2026-11-04) - -> 实现批禁写 `design/`——以下各条由用户裁决后归文档批落文。裁决一条落一条,落完把该条清掉。 - -## 一、design/engineering/architecture/architecture.md(十处) - -1. **:124(BattleFxHost 行)**:「A3 action_economy(queue_ap_delta/charge_layers 读)」依赖句删 charge_layers 读——蓄力层账本已退役(卡①),A3 对 action_economy 依赖余 queue_ap_delta 通道。 -2. **:146(轴戊 EffectPort 句)**:「闭面五键」改「闭面四键」;「facts 取值束 dig_streak/charge_layers/rider_tier_*」删 charge_layers——biz_fact 闭面收窄为 dig_streak/hp_ratio/rider_tier_bonus/rider_tier_crit(卡①,键退役后宿主应答 null)。 -3. **:125(RoundCycle 行)**:「三参直构(host/fx_host_ref/reward_grant_ref——五段构造透传不落成员)」→「引用束注入(编排器 20 键封闭束,解包自用七键后原束分发五段;持有 BattleRefreshFlow);公开方法 3→4(refresh_flow() 读出口);类变量 8→15(贴顶禁再增)」(卡②)。 -4. **:123(BattleRefreshFlow 行)**:「static 两入口收 oc」→「实例束注入 8 键(facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg);宿主 can_refresh/player_request_refresh 单行转发形态不变,经 RoundCycle.refresh_flow() 取件;grant_seg 段间引用直接交接(编排器 _rounds._grant_seg 跨两层链式取段消灭);类变量 0→8」(卡②)。 -5. **:127(settlement/ 五段段类行)**:构造形态句 →「引用束注入(各段断言各自必需键);段类对编排器零引用(\boc\b 零命中——跨件单向接线,注入 Callable 亦属接线面)」(卡②)。 -6. **:122(damage_controller 行)**:类变量 12→13(+facts 构造注入);公开面净增 hero_atk()/static hits_of()/emit_hero_damage_taken 公开化(_emit 去下划线非并存)(卡②)。 -7. **:121(RewardGrant 行)**:+feat_yield_adjust(_feat_yield_adjust 自编排器归位)(卡②)。 -8. **:120(BattleFxHost 行)**:+mark_new_resource_type/+add_bonus_delta 两公开累积方法(类变量 15 不变,方法-only)(卡②)。 -9. **核心层表 A2 minesweeper_rules 行**:公开面 19→21(贴顶超 1——**随卡② S13 停卡呈报待用户裁**:特批准记基线则登记 21 贴顶超阈值态并立还债条目;harmful_variants/harmful_adjacent 自编排器归位)(卡②)。 -10. **核心层表 breakpoint_store 行+save_manager/slot_store 行**:breakpoint_store 公开方法面 5→18(13 新面+bp_read 公开化);save_manager 公开面 7→14(+set_read_only/read_only/reset_to_default/data_snapshot/restore_data+peek_summary_at/rename_overwrite 两更名公开化);slot_store「双向公开面」(+active_path;migrate_legacy_save 公开化;反向私取 21 处清零)(卡②)。 -11. **:119(B1 battle_orchestrator 行)**:「NODE_TYPES 词表双份」表述删句、改「词表单点归 LevelTable(生成器 class_name 直引——S3 豁免面在案形态)」(卡③)。 - -## 二、GDD 确认行 - -- design/gdd/systems/战斗循环/战斗结算.md:**零反哺**(明细流水词表、五段段序、段边界零触碰——零行为变更批,golden 逐位对拍在案)。 -- design/gdd/systems/表现与支撑/数据配置.md:**零反哺**(热重载规格保留原状——P2 卡不收件面)。 -- 术语表:**零新增术语**(引用束注入/读投影/公开方法面均仓库现行词)。 -- 卡④:难度曲线与Boss.md/敌人系统.md/08-Alpha版本标准.md **零动作**(文档批已随写卡段落文,本批零新增反哺)。 - -## 三、待办清单(实现批不碰,归收尾段/用户) - -- 卡② S13 一键(minesweeper_rules 公开方法 21>20):待用户按实现报告 §六裁决;若特批准记基线,建议同批立「minesweeper_rules 公开面还债」条目归后续批。 -- EffectEngine.has_effect_on 零消费方(卡① 备案):是否立死码处置条目,归用户裁。 diff --git a/reports/iter51-实现报告-2026-11-04.md b/reports/iter51-实现报告-2026-11-04.md deleted file mode 100644 index 10c80207..00000000 --- a/reports/iter51-实现报告-2026-11-04.md +++ /dev/null @@ -1,102 +0,0 @@ -# 迭代 51 实现报告(2026-11-04) - -> 四卡:①EffectPort 蓄力层删键批 ②架构审查 5.1 封装修复批 ③架构审查 P2 代码面批 ④L1 深度 3 混编。 -> 工作区=g-plan2-iter51(分支 iter51,基点 63fe2da0);提交序列见 git log(卡① 一笔/卡② 四笔 T1+T2、T3+T4、T5、——/卡③ 一笔/卡④ 一笔)。 -> **卡② 停卡呈报在案(S13 一键待裁)**——见「六、停卡呈报」,合并等用户裁决。 - -## 一、各卡状态 - -| 卡 | 状态 | 提交 | 卡内验证读数 | -|---|---|---|---| -| ① EffectPort 蓄力层删键 | 完成 | ace15701 | effect_port_test 16 checks(含新 V4)/treasure_effects PASS/refresh_feats 85 checks;零残留 grep 达成(例外见决定记录 2);S13 零新违规 | -| ② 5.1 封装修复 | **施工完成·S13 停卡呈报待裁** | 568c474d/d43de922/0e1763bd | golden 主证改前固化改后逐位同(seed 4242,3 checks)/settle_segments 18 checks/adventure_feats 138 checks/battle_settlement 七套件绿/存档五套件绿(17+97+22+13+29)/sweep 38 checks/冒烟绿;**S13 新违规 1 条**(minesweeper_rules 公开方法 21>20 贴顶——§六) | -| ③ P2 NODE_TYPES 单点化 | 完成 | 52bb63d7 | encapsulation_p2_sweep 6 checks/node_map_determinism 21 checks(同种子逐位——零行为变更主证)/冒烟绿;S13 键零变化 | -| ④ L1 深度 3 混编 | 完成 | afaab6a7 | table_structure_guard 83 checks(v1–v6)/round_flow 136 checks(含 _smoke_enemy_buff_targets_self 种子搜索命中)/level_shop 195 checks/damage_controller 50/battle_narrator 22/config_db_test 29/config_db_resolver 13;全仓 hobgoblin 零残留(例外见决定记录 7);Alpha 零读数(静态论证) | - -## 二、实际改动—验收对照表 - -### 卡①(对照表 3 行全绿) - -| # | 需求点 | 改动 | 验收测试(落点+尾缀) | 真实入口 | 判据绑定面 | 结果 | -|---|---|---|---|---|---|---| -| 1 | 事实应答面收窄 | battle_fx_host.gd biz_fact/_fx_fact 两分支删 | effect_port_test.gd:131「V4 闭面收窄」 | begin_battle 真实装配链取宿主 biz_fact | 键在→返 0、键退役→null | 绿(16 checks 内) | -| 2 | 账本与转蓄分支退役、签名简化 | action_economy.gd finish_round_stop 纯 RAP 转存+grant_segment 调用简化 | treasure_effects_test.gd ⑥ 三断言(132/138/144 调用单参化,期望值零变) | request_stop→round_end→finish_round_stop 真实停手路径 | RAP 转存读数 4/5/3 逐位零漂移 | 绿(PASS) | -| 3 | 事件/事实两词表位退役 | effect_engine.gd EVENTS/FACTS 删位 | 静态判据+零残留 grep+S13+--code 域(合回 CI) | —(词表常量面) | 静态判据+评审注记(申报观测面局限在卡) | 绿(grep 零命中) | - -### 卡②(对照表 R1–R6 全绿,全部「非行为改动」行) - -| # | 需求点 | 改动 | 验收测试 | 真实入口 | 判据绑定面 | 结果 | -|---|---|---|---|---|---|---| -| R1 | 七段解耦+束交付 | 七文件重写+编排器 20 键束+三调用点 | settlement_wiring_snapshot_test〔封装5.1 V1〕+settle_segments_test 束构造/同一性断言+round_flow/seal_polarity/refresh_feats 既有全套 | begin_battle→settle_batch→player_request_refresh 全公开入口 | 同种子行流逐位同 golden+束引用同一性+\boc\b 七文件零命中 | 绿 | -| R2 | 搬家五方法归位 | damage_controller +hero_atk/hits_of/emit 公开化+facts;reward_grant +feat_yield_adjust;minesweeper +harmful_variants/harmful_adjacent;编排器五方法删 | golden V1(五方法消费路径全在行流内)+roll_multiplier_test 既有+sweep V3 | begin_battle/授予段真实结算链 | 行流逐位同+五方法宿主定义唯一 | 绿 | -| R3 | grant→BattleFxHost 累积方法 | battle_fx_host +mark_new_resource_type/+add_bonus_delta;grant 四处改址 | seal_polarity/round_flow 既有回归+sweep V6 | 授予段真实结算链 | bonus_delta 轴累积与首见判定逐位同+grant 源零私键 | 绿 | -| R4 | adventure_runner→BreakpointStore | breakpoint_store +13 公开方法(bp_read 公开化);adventure_runner 37 处改址 | save_rng_roundtrip_test 两断言既有+sweep V4 | resume_adventure/start_adventure→write_to_disk/load_from_disk | 断点续玩逐位同+源零私取 | 绿 | -| R5 | save_manager↔SlotStore 双向 | slot_store +active_path/migrate 公开化+21 处改址;save_manager +5 公开方法/2 更名+九行改址 | save_manager_domain_test+save_slot_isolation_test+sweep V5 | write_to_disk/load_from_disk/_ready 迁移口/activate_slot/peek_slot_summary | 写盘读回迁移激活窥视逐位同+双向源零私取 | 绿 | -| R6 | config_db→三表挂载 | 三 schema +bind_resolver;config_db 三行改址 | config_db_resolver_test 既有全套+sweep V7 | ConfigDB.load_tables→resolve_* | 行集解析与难度 clamp 逐位同+源零直写 | 绿 | - -**零行为变更证明**:T1 golden 改前基线固化(seed 4242 未改树转储 7444 字符 JSON 填入常量)、改后全程绿=行流/快照/终局载荷/presentation 逐位同;既有套件零转红;sweep 恒零。 - -### 卡③(对照表 R1 全绿) - -| # | 需求点 | 改动 | 验收测试 | 真实入口 | 判据绑定面 | 结果 | -|---|---|---|---|---|---|---| -| R1 | NODE_TYPES 单点化 | node_map_generator :10 删+:135 改 LevelTable.NODE_TYPES.has | encapsulation_p2_sweep_test〔架构P2 V1/V2〕+node_map_determinism_test 既有全套 | LevelRunner 关卡推进→节点图生成;LevelTable 表行加载校验 | 词表定义唯一落点(正锚计数=1)+生成链同种子逐位同 | 绿 | - -### 卡④(对照表 R1–R10 全绿) - -| # | 需求点 | 改动 | 验收测试(尾缀〔L1混编 vN〕) | 真实入口 | 判据绑定面 | 结果 | -|---|---|---|---|---|---|---| -| R1 | 行数与键序 | enemy.tres (1,3) 一行换两行 | table_structure_guard「敌人表 29 行且键序=覆盖表序((1,3) 两行相邻、熊地精在前)」 | ConfigDb.load_tables→EnemyTable.rows | 遍历 rows 拼四段串逐行比对 | 绿(83 checks 内) | -| R2 | 混编构成与行序 | 同上 | 同文件「(1,3) 行集恰两行、enemy_id 序列==[bugbear,goblin]」 | resolve_rowset(1,3,"battle") | 返回长度与 enemy_id 序列 | 绿 | -| R3 | 静态最坏骰和 | 同上 | 同文件「Σ(unit_count_max,固定行缺省)==3」 | 同上 | 求和值==3 | 绿 | -| R4 | 行值整行复用 | 同上 | 同文件「(1,3) 两行逐字段==(1,2)/(1,0) 行」 | EnemyTable.rows | 字段逐项比对零字面量 | 绿 | -| R5 | 零残留 | 表+七文件 | 同文件「表内 enemy_id 集合不含 hobgoblin」+T2 回读命令 | rows 遍历+grep | 集合不含+grep 空 | 绿 | -| R6 | 混编下 spawn 数全域内 | round_flow :312-317 口径逐行求和 | _smoke_encounter_roll 全深度×12 种子扫描 | begin_battle→presentation_snapshot().enemies.size() | 逐场数量∈[Σunit_count,Σunit_count_max] 且 ≥1 | 绿(136 checks 内) | -| R7 | 双池各自独立 | 表两行 | _smoke_skill_pool_data「(1,3) 两行池模板」 | 表读取 | 键与权重逐项 | 绿 | -| R8 | 注入段还原行数 | level_shop :727 | _smoke_level_graph「表回 29 行」 | 同套件注入/还原流程 | rows.size()==29 | 绿 | -| R9 | 深度 4/5 落带不变 | 表(零触碰) | table_guard「(1,4)/(1,5) battle 行集==单行 bugbear(band 6)」 | resolve_rowset(1,4/5) | 长度与 enemy_id、band 归属 | 绿 | -| R10 | 深度 3 最少 2 敌(回归护栏) | 两处夹具注释 | damage_controller_test _test_pool_consumption_per_unit+battle_narrator_test _test_snapshot_effects_per_unit 在混编行集下仍绿 | UnitContainer.spawn(1,3) | all_enemies().size()≥2 | 绿 | - -## 三、S13 对账 - -- 改前基线:gdstyle_gate 5 键基线内、基线外 0(T1 现场读数;adventure_runner 类变量 23 照登)。 -- 改后:**基线外新违规 1 条=game/core/minesweeper_rules.gd|quality/max-public-methods(21>20 贴顶)**——卡② 停卡呈报项(§六)。其余全部触件 S13 键只降不升:action_economy 删 2 成员、七段束解包全在预算内(grant 12/combat 10/verdict 5/aura 1/decay 2/round_cycle 15 贴顶/battle_refresh_flow 8)、breakpoint_store 类变量 15 贴顶方法-only、编排器 15 不变。 - -## 四、实机目检总清单(真人检测只登记不阻塞) - -- 卡①②③:无实机目检项(死码删除/封装收口/词表单点化——零玩家可观测提交面;卡② 零行为变更由 golden 逐位对拍承载)。 -- 卡④(抄体验验证清单 ⑱ L1 节点 3 三项):① 混编遭遇实机目检(L1 节点 3 遇敌=熊地精 1+地精 1–2,默认目标=熊地精);② 开战叙述首行=熊地精;③ 战斗结算面两种数量语义并存目检。读数义务:Alpha 零读数(2026-11-04 用户裁),带位移读数归数值统一修正批。 - -## 五、能力清单核对 - -game/tests/README.md:本批零编辑(四卡对照表能力行全部「既有」;测试函数名零改名——settle_segments/round_flow/level_shop/damage_controller/battle_narrator 代表用例名在册不变;卡② 新增两测试件按「经宿主链覆盖」申报=snapshot 测试挂 R1 行 begin_battle 全结算链、sweep 挂各 R 行源扫描)。名实核对一致,无回表项。 - -## 六、停卡呈报(卡② S13 一键——实现段零裁断,缺陷移交) - -**分歧点**:minesweeper_rules.gd 公开方法数。写卡 §14 申报「18→20 贴顶(+2)」;master 基点实数 **19**(`git show master:game/core/minesweeper_rules.gd` 逐 func 清点),本批 +2 搬家方法(harmful_variants/harmful_adjacent)后 **21>20**(S13 默认阈值)——gdstyle_gate 基线外新违规 1 条。 - -**两种读法**: -- 读法 A(写卡考证误差 1,预算按实况校准为 19→21):+2 是卡② 五方法归位裁决的直接后果(grant_segment :87/:339 两消费方直调,两方法都必须公开,少任何一件破坏搬家合同);21 超阈值需用户特批准记基线(迭代 38 先例 c4c69701——S13 恶化经用户特批准记入基线存量红)。但 static_scan S13 头注「登记值只准随还债下降,禁抬高到现状」——特批是唯一合法通道,闸件纪律不因批内需要自动放行。 -- 读法 B(卡面预算准、实现走形):不存在第三形态——拆函数降计数=实现段自行拆件(L3 ③禁);改闸件豁免=禁(卡② §13;先例迭代 38 卡② 停卡);把方法挪去 BoardData 或他件=职责面重设计(上游裁决)。 - -**建议案**:读法 A——用户特批准记基线 1 键(minesweeper_rules 公开方法 21),理由:+2 归位是 2026-11-03 会话裁决的机械承载、消费面决定两方法必须公开、基点考证误差 1 属写卡段缺陷(非实现超预算)、特批有迭代 38 先例。批后可开「minesweeper_rules 公开面还债」条目(归后续批:如 settle_batch/fx_reveal 族收敛)随还债降键。 - -**该红不阻塞的实证**:卡② 全部验收断言绿、golden 零漂移、sweep 恒零——S13 是结构预算闸不是行为闸;红项单键、修复路径已指认(特批记基线或后续还债批)。用户裁决后本迭代即可走独立验收审计→合并;若用户裁读法 B(拆面),开修复 worktree `fix-iter51-s13` 处置后重审计。 - -## 七、实现决定记录(代拍板白名单外备案) - -1. **卡① has_effect_on 零消费方保留**:删 grant_segment 调用后 `EffectEngine.has_effect_on` 全仓零调用。卡面 :203 处置=注释改写、函数保留(T0 步骤明写「:203 句改……」未列函数删除),未代裁扩大删除面。后续可另立待办处置(纯框架 API 死码)。 -2. **卡① 零残留 grep 唯一例外**:effect_port_test.gd:131 V4 断言文案与 `biz_fact("charge_layers")` 参数引证退役键名——卡面 T0 定死的断言文案(证明「键退役→null」必须引用键名),断言优先于 grep 字面。 -3. **卡① 三处 swap 域描述词换代**:run_all.gd:213 manifest 注释、effect_port_test :67/:83 的「蓄力层」简称改「蓄力周转」(swap_charge_layers 在役域,词面清旧称;断言判据零变——:83 属断言文案措辞,判据 `v_c - v_0 >= 1` 不动)。 -4. **卡② oc._ 计数 102 vs 卡面 105**:迭代 50 甲案删 attack_segments 分支所致(卡面预告的漂移,grep 符号锚定位,禁改判据)。 -5. **卡④ 曲线护栏逐带累加**:table_structure_guard_test 第一关总量比断言原单行覆盖口径在 (1,3) 双行同带下失真(后行覆盖前行→band3 总量 12→72 比值 6.00 假红);改同带逐行累加(band3=22×1+8×1.5=34,比值 2.12)——与卡④ R6 已裁「逐行求和与 spawn 守卫同口径」同构,阈值 2.5 零变化,非放宽。期望序列文案 12/10/22/35/72→12/10/22/34/72(34=混编实况)。 -6. **卡④ level_shop 白盒抬 GP**:(1,3) 混编改战斗流随机消费序→_smoke_shop_pool 种子扫描选中的场景 gp 分布漂移(进店刷新扣费后 gp=1<全架最低价)→「可购卡在架」假红。修法=③ 买段前白盒 `r8.gp += 128`(A 银段 :1345 同款先例「直改测试基建——非生产行为」),买链判据零变化;MC-1 刷新段保持原位(进店未买态语义),未动段落序。 -7. **卡④ R5 零残留 grep 唯一例外**:table_structure_guard_test 新增 v5 断言文案与 `id_set.has("hobgoblin")` 引证退役键名(卡面 §八 R5 定死验收文案)。 -8. **卡② 测试连带三处迁移**(写卡 §3.2 漏盘点):adventure_feats_test :791 与 level_shop_economy_test :1484 `a._bp_store._pending_next_level` → `pending_next_level()`;feat_vouchers_test :81 `_loadout_seeded = false` → `set_loadout_seeded(false)`(断点公开面在用,引用形态迁移非削弱);damage_controller_test :55 直构夹具补 BattleFacts 第五参(facts 构造注入——hero_atk 消费面,夹具空 encounter=表值缺省)。**审计订正**:level_shop 一处曾随回滚实验丢失(卡④ 提交后由独立验收审计查出,:1484 补迁移复跑 195 checks 绿——动态访问失效成员使停点分支恒假、依赖种子被扫描跳过掩盖红,属申报落实的漏项非削弱)。 -9. **卡② snapshot 测试对拍形态**:GOLDEN 三引号 raw string 首尾各带换行(多行字符串语法载体),比较两侧 strip_edges 后逐位等——判据零变(内容逐位),语法载体适配。 - -## 八、遗留读数义务与反哺点 - -- 遗留读数:卡④ Alpha 零读数(归数值统一修正批);卡② S13 一键(§六待裁)。 -- 反哺点:见 `reports/iter51-反哺点-2026-11-04.md`(architecture.md 十处+GDD/术语确认行)。 -- 待办清单条目状态推进与 roadmap「当前位」换代:归文档批(本批禁写)。 diff --git a/reports/iter51-验收审计-2026-11-04.md b/reports/iter51-验收审计-2026-11-04.md deleted file mode 100644 index 3cdafe66..00000000 --- a/reports/iter51-验收审计-2026-11-04.md +++ /dev/null @@ -1,190 +0,0 @@ -# 迭代 51 独立验收审计(2026-11-04) - -> 审计身份:独立会话审计员,未参与本批实现(实现由另一会话完成)。 -> 工作区:D:\code\g-plan2-iter51(分支 iter51,基点 master=63fe2da0,diff 范围 63fe2da0..HEAD,HEAD=ef3cd8db,工作树 clean)。 -> 审计范围:四张需求卡片(①EffectPort 蓄力层删键批 ②架构审查 5.1 封装修复批 ③架构审查 P2 代码面批 ④L1 深度 3 混编)+实现报告 §一–§八+全量 diff 逐文件核对(48 件,+1594/−474)。 -> **验收档:四卡均未申报验收档——按高档全量执行**(对照表逐行核对+判据绑定面抽查+最窄测试复跑抽查+逐处削弱检查+直查例外核对+S13 对账复核)。 -> 纪律:只读生产码与测试;禁变异类测试、禁撤销类取证;本报告为本审计唯一写入文件。 - -## 一、最窄测试复跑读数 - -环境:Godot 4.7.2 console 版(D:\code\godot\Godot_v4.7.2-stable_win64_console.exe)headless,工作目录 game/,`--script` 单件直跑;跑前确认 `game/.godot/global_script_class_cache.cfg` 在位(直接跑)。退出码 0=绿。 - -| 套件 | 归属 | 退出码 | 读数 | -|---|---|---|---| -| effect_port_test | 卡① V4(五件套) | 0 | all green(16 checks,V4 断言在列全 PASS) | -| settlement_wiring_snapshot_test | 卡② R1 golden 主证(五件套) | 0 | all green(3 checks,golden 逐位同) | -| encapsulation_sweep_test | 卡② V2–V7(五件套) | 0 | all green(38 checks) | -| encapsulation_p2_sweep_test | 卡③ V1/V2(五件套) | 0 | all green(6 checks) | -| table_structure_guard_test | 卡④ v1–v6(五件套) | 0 | all green(83 checks,v1–v6 六条全 PASS) | -| round_flow_skill_pool_test | 审计自选(卡④ R6/R7/R10) | 0 | all green(136 checks,含 buff_targets_self 种子搜索命中 seed=1) | -| save_rng_roundtrip_test | 审计自选(卡② R4) | 0 | all green(17 checks) | -| treasure_effects_test | 卡① 行 2 | 0 | PASS | -| settle_segments_test | 卡② R1 | 0 | all green(18 checks) | -| adventure_feats_test | 卡② R2 | 0 | all green(138 checks) | -| seal_polarity_test | 卡② R3 | 0 | all green(70 checks) | -| refresh_feats_test | 卡① 契约 2/卡② R1 | 0 | all green(85 checks) | -| roll_multiplier_test | 卡② R2 | 0 | all green(42 checks) | -| config_db_resolver_test | 卡② R6/卡④ | 0 | all green(13 checks) | -| config_db_test | 卡④ | 0 | all green(29 checks) | -| save_manager_domain_test | 卡② R5 | 0 | all green(97 checks) | -| save_slot_isolation_test | 卡② R5 | 0 | all green(22 checks) | -| feat_vouchers_test | 卡② 连带迁移 | 0 | all green(49 checks) | -| damage_controller_test | 卡④ R10 | 0 | all green(50 checks) | -| battle_narrator_test | 卡④ R10 | 0 | all green(22 checks) | -| level_shop_economy_test | 卡④ R8 | 0 | all green(195 checks) | -| node_map_determinism_test | 卡③ 零行为主证 | 0 | all green(21 checks) | - -## 二、卡① EffectPort 蓄力层删键批 - -### 对照表逐行核对(3 行) - -| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | -|---|---|---|---|---| -| 1 | effect_port_test.gd:131 断言在(V3 组 `_test_biz_fact_face_closed` 末尾) | 「V4 闭面收窄:charge_layers 键退役——宿主事实应答 null」grep 命中 :131 | begin_battle 真实装配链(:102 `orch.begin_battle(_enc([]))` 受理后 :131 取 `orch.fx_host().biz_fact("charge_layers")`——fx_host() 公开访问器+biz_fact 公开方法,未绕过装配链直调下层) | 通过 | -| 2 | treasure_effects_test 三断言在,期望值 rap==4/5/3 逐字未动(diff 仅调用实参行) | 无新增尾缀(既有断言回归锚,卡面即如此定) | request_stop→finish_round_stop 真实停手路径;直构 ActionEconomy 夹具=卡面 §8 测试方式两分法明示的调试接口验证形态,且卡面 §7 T0 逐字定死该三行改单参——非绕过 | 通过 | -| 3 | 静态判据:effect_engine.gd EVENTS `charge_full` 位、FACTS `charge_layers` 位删除(diff 证实 :14/:59 两处删位) | —(词表常量面,卡面明示无独立运行期提交面) | —(静态判据+评审注记,卡面申报观测面局限在案) | 通过 | - -零残留复核:`grep -rn "charge_layers|charge_full|CHARGE_LAYER|蓄力层|转蓄力|厚积薄发|铁壁|整备军备" game/ --include=*.gd --include=*.tres` 全部命中仅在 swap_charge_layers 在役域(feats.tres :1018-1056 六行、refresh_feats_test `test_charge_layers_consumed_same_round` 函数名与其断言、effect_port_test :16 注释)+V4 断言键名引证(effect_port_test :131)——前者=卡面 §1 完成定义明写豁免域,后者=报告决定记录 2 申报的唯一例外(证明「键退役→null」必须引用键名)。达卡面 T1 零残留判据。 - -### 削弱检查(逐处) - -| diff 处 | 申报出处 | 结论 | -|---|---|---| -| treasure_effects_test 三处 finish_round_stop(false,N)→(N)(:132/:138/:144) | 卡① §8 测试削弱申报 | 通过——断言期望值与检查语义逐字零变化 | -| effect_port_test :57 注释+:83 断言文案「蓄力层→蓄力周转」 | 卡① §8 申报(V1 两处文案) | 通过——判据 `v_c - v_0 >= 1` 不动 | -| effect_port_test :16/:103 误标注释改写 | 卡① §7 T1 申报(三处注释) | 通过——注释级 | -| run_all.gd:213 manifest 注释「蓄力层→蓄力周转」 | 报告决定记录 3 | 通过——注释级,判据零变 | -| has_effect_on 函数保留(grant_segment 调用删后全仓零调用方) | 报告决定记录 1(卡面 T0 :203 只列注释改写、未列函数删除) | 通过——未代裁扩大删除面,与卡面一致 | - -无未申报删改。 - -### 直查例外核对 - -对照表三行均未声明「内部字段直查=受控例外」:行 1 走公开面(fx_host()/biz_fact);行 2 直构夹具=卡面 §8 两分法明示形态;行 3 静态面。无需例外注记,判定=通过。 - -## 三、卡② 架构审查 5.1 封装修复批 - -### 对照表逐行核对(R1–R6) - -| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | -|---|---|---|---|---| -| R1 | settlement_wiring_snapshot_test.gd:456 主对拍在(GOLDEN 常量 :22-416 固化,seed 4242;dump 通道 :443-448);settle_segments_test :30-34 束构造 5 条+:38-44 同一性 6 条在 | 「封装5.1 V1」:4/:456 命中 | begin_battle→minesweeper.settle_batch→player_request_refresh 全公开入口;段类消费全经真实编排器接线(settle_segments 直构束=卡面 §8 申报 1 的适配形态) | 通过 | -| R2 | golden V1 覆盖五方法消费路径(主角攻击 base_value/奖励乘区/terrain_kind 事实/hero_atk override 行均在 golden 行流内);roll_multiplier_test 既有回归绿;sweep V3 编排器五方法定义零命中 | sweep :69-70 五方法名零命中断言(〔封装5.1〕尾缀 :36/:42 模板命中 38 处) | begin_battle spawn 经 `_damage.hero_atk()`(battle_orchestrator.gd diff :285 证实改指),未绕过 | 通过 | -| R3 | seal_polarity_test 70 checks/round_flow 136 checks 绿+sweep V6(grant 两私键零命中+mark_new_resource_type 正锚 :100-101) | 〔封装5.1〕尾缀在案 | 授予段真实结算链(两套件全走 begin_battle) | 通过 | -| R4 | save_rng_roundtrip_test diff 零改动(未触碰)、17 checks 绿;sweep V4(adventure_runner `_bp_store\._` 零命中+`_bp_store\.bp_read\(` 正锚 :78-79) | 〔封装5.1〕尾缀在案 | AdventureRunner.resume_adventure/start_adventure→write_to_disk/load_from_disk(integration 件真实入口) | 通过 | -| R5 | save_manager_domain_test 97/save_slot_isolation_test 22 绿;sweep V5(save_manager `slots\._` 零命中+active_path 正锚;slot_store `_save\._` 零命中+七公开面正锚 :84-92) | 〔封装5.1〕尾缀在案 | write_to_disk/load_from_disk/_ready 迁移口/activate_slot/peek_slot_summary(两套件承载) | 通过 | -| R6 | config_db_resolver_test 13 checks 绿(rowset 保序/行类路由/难度 clamp);sweep V7(config_db `\._resolver\s*=` 零命中+`bind_resolver\(_resolver\)` 正锚 :109-110) | 〔封装5.1〕尾缀在案 | ConfigDB.load_tables→resolve_*(三 schema bind_resolver 类型环当轮真实跑通——T4 义务承载) | 通过 | - -零行为变更主证复核:golden 对拍断言绿(本审计复跑);段类对编排器引用归零由 sweep V2 七文件 `\boc\b` 零命中承载;束 20 键组装与五方法删除、RoundCycle/BattleRefreshFlow 形态换代在 diff 逐文件证实。 - -### 削弱检查(逐处) - -| diff 处 | 申报出处 | 结论 | -|---|---|---| -| settle_segments_test :20-24 五段直构断言改束构造成功(:30-34) | 卡② §8 申报 1 | 通过——段类可独立实例化断言意图保持 | -| settle_segments_test :28-34 宿主回指 6 条改注入同一性(:38-44——round_cycle.facts/grant.facts/aura 回调对象 get_object()==oc/combat.fx_host/decay.status_effects/verdict.units) | 卡② §8 申报 2 | 通过——6 条全数对上,强度对应回指 | -| adventure_feats_test :671/:691/:727 三处 orch._hits_of→DamageController.hits_of | 卡② §8 申报 3 | 通过——断言内容(attack_bonus 命中过滤)零改动 | -| adventure_feats_test :791 `a._bp_store._pending_next_level`→`pending_next_level()` | 报告 §七 8 | 落地证实(grep 现值为公开方法调用) | -| feat_vouchers_test :81 `_loadout_seeded = false`→`set_loadout_seeded(false)` | 报告 §七 8 | 落地证实 | -| damage_controller_test :56 直构夹具补 BattleFacts 第五参 | 报告 §七 8 | 通过——夹具空 encounter=表值缺省,断言零改动 | -| snapshot 测试 strip_edges 对拍(:449-451) | 报告 §七 9 | 通过——内容逐位判据零变,三引号语法载体适配 | -| **level_shop_economy_test :1484 `a._bp_store._pending_next_level`——报告 §七 8 与卡④ commit信息均申报为已迁移,实际未改** | —— | **申报失实,见第六节;非削弱(该行零改动=零删改)** | - -无未申报删改、无阈值放宽、无扫描面缩小。 - -### 直查例外核对 - -settle_segments_test 直查 `oc._facts`/`oc._rounds._grant_seg.*` 等编排器内部(同一性断言)=卡② §5 明示受控例外(「sim/probe/tests 的测量面直构惯例与测试受控例外——零清扫:orch._facts/orch._rounds/orch._emit_row 等测试直连维持现状」)。报告 §七 8 三处测试直连迁移=附加申报(其中一处未落地,见第六节,且该处属同一受控例外面、运行态零差异)。例外均有注记,判定=通过。 - -## 四、卡③ 架构审查 P2 代码面批 - -### 对照表逐行核对(R1) - -| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | -|---|---|---|---|---| -| R1 | encapsulation_p2_sweep_test V1(:40-41 生成器 `^const NODE_TYPES` 零命中+`LevelTable\.NODE_TYPES\.has\(` 正锚)/V2(:56-58 生产三目录 `NODE_TYPES` 定义恰 1 处且落 data/schemas/level_table.gd);node_map_determinism_test 既有全套 21 checks 绿 | 「〔架构P2〕」尾缀 :27/:32/:56/:58 命中 | 源扫描(静态)+LevelRunner 关卡推进生成链(determinism 同种子逐位——零行为变更主证) | 通过 | - -生产面独立复核:`grep -rnE "NODE_TYPES\s*:?\s*=" core data presentation` 唯一命中 data/schemas/level_table.gd:11;node_map_generator.gd 本地 const 删、:135 改 `LevelTable.NODE_TYPES.has(...)`(diff 证实);S3 schema class_name 直引豁免面形态(卡面 §3 在案)。 - -### 削弱检查 - -卡面 §8 申报零条;diff 中本卡测试改动仅新增 sweep 件+run_all manifest +1 行——零删改既有断言。通过。 - -### 直查例外核对 - -纯源扫描与既有套件回归,无内部字段直查。通过。 - -## 五、卡④ L1 深度 3 混编 - -### 对照表逐行核对(R1–R10) - -| 行 | 验收测试存在性 | 尾缀 grep | 真实入口判定 | 结果 | -|---|---|---|---|---| -| R1 | table_structure_guard_test :232 v1(29 行键序=(1,3) 两行相邻、熊地精在前) | 〔L1混编 v1〕:232 命中 | ConfigDb.load_tables→EnemyTable.rows 遍历拼四段串逐行比对 | 通过 | -| R2 | :242 v2(行集恰两行、enemy_id 序列==[bugbear,goblin]) | 〔L1混编 v2〕:242 命中 | `resolve_rowset(1,3,"battle")` 返回序列——判据绑返回长度与 enemy_id 序列(被测数据改动特有提交面) | 通过 | -| R3 | :244 v3(Σ(unit_count_max,固定行缺省)==3) | 〔L1混编 v3〕:244 命中 | 同上求和值==3 | 通过 | -| R4 | :258 v4(两行逐字段==(1,2)/(1,0) 行,零字面量) | 〔L1混编 v4〕:258 命中 | EnemyTable.rows 字段逐项比对 | 通过 | -| R5 | :263 v5(表内 enemy_id 集合不含 hobgoblin)+T2 回读命令 | 〔L1混编 v5〕:263 命中 | rows 遍历+grep | 通过 | -| R6 | round_flow :320 全域扫描逐行求和(72 场全绿) | 断言文案「逐行求和」:320 命中 | begin_battle→presentation_snapshot().enemies.size() | 通过 | -| R7 | round_flow :575-576 池模板两键(bugbear 6/2/2+goblin 9/1/1,顺序按表行序) | :575-576 两键在案 | 表读取逐键逐权重比对 | 通过 | -| R8 | level_shop :727「表回 29 行」 | :727 断言在案 | 同套件既有注入/还原流程(rows.size()==29) | 通过 | -| R9 | :270 v6((1,4)/(1,5) battle 行集==单行 bugbear(band 6)) | 〔L1混编 v6〕:270 命中 | resolve_rowset(1,4/5,"battle") | 通过 | -| R10 | damage_controller_test _test_pool_consumption_per_unit/battle_narrator_test :114-115 _test_snapshot_effects_per_unit 混编行集下绿(50/22 checks) | —(回归护栏行,卡面即不设新尾缀) | UnitContainer.spawn(1,3)→all_enemies().size()≥2 | 通过 | - -行值整行复用独立复核:enemy.tres (1,3) bugbear 行(:82-104)与 (1,2) 行(:58-80)逐字段一致(hp22/atk1/atk 0-2/池 attack6-charge2-heavy2/normal/light/ac2/unit_count 1 无 unit_count_max);(1,3) goblin 行(:106-129)与 (1,0) 行(:8-31)逐字段一致(hp8/池 attack9-lunge1-desperate_guard1/normal/light/ac0/unit_count 1-2)。行序 bugbear 在前、两行相邻。表注四处处置(实体名修正段删/首批投放段尾追加/N1 括注保留/缺口句换新注)diff 逐处证实。零残留复核:全仓 `hobgoblin` grep(data/core/presentation/probe/sim/tests/tools)唯一命中=v5 断言自身键名引证(table_structure_guard_test :259 注释+:263 断言)=报告决定记录 7 申报的唯一例外。 - -### 削弱检查(逐处) - -| diff 处 | 申报出处 | 结论 | -|---|---|---| -| table_guard :234-241 hobgoblin 留任断言删(原〔fv12〕) | 卡④ §十二 申报① | 通过——钉已退役事实;原位换 v2–v6 五条新断言,覆盖强度净增 | -| round_flow :312-317 首行区间改逐行求和 | 卡④ §十二 申报② | 通过——与 unit_container spawn 守卫同口径,72 场全绿,双行行集下非放宽(旧口径必红) | -| round_flow :615 文案二十八行九种→二十九行十种 | 卡④ §十二 申报③ | 通过——只改文案不改判据 | -| static_scan.sh 删 FNR==891 豁免(:145 头注+:222 执行点) | 卡④ §十二 申报④ | 通过——对象行已随表注段删除,留置会误放别行 | -| 曲线护栏单行覆盖口径改逐带累加(期望序列 12/10/22/35/72→12/10/22/34/72,阈值 2.5 不动) | 报告 §七 5 | 通过——(1,3) 双行同带下旧「后行覆盖前行」口径失真(band3 总量 12→72 比值假红),新口径=同带逐行累加、与 R6 逐行求和同构;阈值零变化 | -| level_shop :1314-1324 白盒抬 GP 128+文案改 | 报告 §七 6 | 通过——测试基建直改(A 银段 :1345 同款先例在案),买链判据零变化 | -| config_db_resolver_test 合成行 hobgoblin→giant_spider(:44,三条期望串同步) | 卡④ §七 T2 明写(并明写不换 bugbear 的理由) | 通过——夹具级,断言结构零变 | -| config_db_test :222 「不相邻」样本换 giant_spider | 卡④ §七 T2 明写 | 通过——同上 | -| damage_controller_test :414/battle_narrator_test :114 两处夹具注释改述 | 卡④ §七 T2 明写(断言零改动) | 通过 | -| doc_term_audit.py :144 注释敌种枚举去 hobgoblin | 卡④ §七 T2 明写 | 通过——注释级 | - -无未申报删改、无阈值放宽、无未申报扫描面缩小。 - -### 直查例外核对 - -卡④ 判据全部绑公开面(rows/resolve_rowset 返回序列/presentation_snapshot/UnitContainer.spawn)——无需例外注记。通过。 - -## 六、申报失实项(非验收缺漏,需订正) - -**实现报告 §七 第 8 条与卡④ commit afaab6a7 提交信息均申报「卡②断点公开面三处测试直连迁移(pending_next_level×2/loadout_seeded)」,实际落地两处:** - -- adventure_feats_test :791 `a._bp_store.pending_next_level()`——已落地; -- feat_vouchers_test :81 `a._bp_store.set_loadout_seeded(false)`——已落地; -- level_shop_economy_test :1484 `if a._bp_store._pending_next_level:`——**未改**(该文件 diff 全量核对仅 :724-727 与 :1314-1324 两处触碰)。 - -定性与影响:非验收缺漏——该行零改动=无断言删改、无削弱;测试侧 `_bp_store._` 直查属卡② §5 明示受控例外面;BreakpointStore `_pending_next_level` 成员保留(breakpoint_store.gd:60),运行态与改前逐位同,套件全绿(level_shop 195 checks)。但实现报告与提交信息的该条申报与实况不符,构成产出物事实错误——建议订正实现报告 §七 第 8 条(改为「两处落地、level_shop 一处未迁」);commit 信息不可改,以本节记录在案。 - -## 七、S13 对账复核 - -实测 `python tools/gdstyle_gate.py`(工作目录 game/,退出码 1): - -``` -[gdstyle_gate] 扫描面 158 个 .gd;命中 6 条,基线内 5 条,基线外新违规 1 条,较登记值恶化 0 条 - FAIL 新增 game/core/minesweeper_rules.gd|quality/max-public-methods -[gdstyle_gate] FAIL — 基线外新违规 1 条/恶化 0 条——新码须修到基线口径内,恶化须还债或经用户批准随批重生成基线 -``` - -与实现报告 §六 逐字一致。独立考证:基点 63fe2da0 的 minesweeper_rules.gd 公开方法实数 19(`git show 63fe2da0:game/core/minesweeper_rules.gd` 逐 func 清点);HEAD 21(含新增 harmful_variants/harmful_adjacent 两搬家方法,diff :58/:70 证实)→ 21>20(S13 默认阈值)。其余触件只降不升(action_economy 类变量 14→12 实测;gdstyle 无第二键恶化)。 - -该项为实现报告 §六已停卡呈报待用户裁的结构闸项(写卡考证 18 失实、基点实数 19、+2 搬家方法),**不属于验收闭合缺陷**——本审计如实记录其状态与呈报位置,不计入审计终判。**合并前置条件=用户对该键的裁决**(特批准记基线随还债降键,或裁实现报告 §六读法 B 开 fix-iter51-s13 修复批后重审计)。 - -## 八、终判 - -**通过**——四卡对照表全部行(卡① 3 行/卡② R1–R6/卡③ R1/卡④ R1–R10)验收断言真实存在、尾缀可 grep 定位、真实入口未绕过被测改动点、判据绑被测改动特有的可观测提交面;最窄复跑五件套+审计自选两件+对照表引用全部其余套件绿(22 件全 0 退出码);测试削弱全部落在四卡申报清单与实现报告决定记录内,零未申报删改;直查例外均有注记;卡① 蓄力层族与卡④ hobgoblin 零残留 grep 达成且例外均为申报例外。 - -记录在案三项(均不改变终判): - -1. **S13 一键**(minesweeper_rules 公开方法 21>20)——停卡呈报在案(实现报告 §六)。**合并前置条件=用户对该键的裁决**;裁决后按既定通道走。 -2. **申报失实一项**——实现报告 §七 第 8 条「三处测试直连迁移」实际落地两处(level_shop_economy_test :1484 未迁),建议订正报告;非验收缺漏、非测试削弱。 -3. 卡② golden 改前固化读数以 GOLDEN 常量在案(settlement_wiring_snapshot_test.gd:22-416,seed 4242),改后对拍绿——对拍读数在案义务已履行。 diff --git a/roadmap.md b/roadmap.md index 241a6335..66c9fbac 100644 --- a/roadmap.md +++ b/roadmap.md @@ -5,8 +5,8 @@ ## 当前位(只回答「现在该做什么」;三行内随批次换代,禁止在此扩写成状态快照) - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 -- **迭代位**:迭代 1–50 全部已完成——迭代 50(敌方状态个体化+断言补齐+架构审查 3.1+3.2 整改三卡)2026-11-03 合回(合并提交 8d3a12ec,独立验收审计两轮终判通过〔首轮缺漏三条→整改笔 6cb1cf5e→复验通过〕),批后收尾闭合(2026-11-03):反哺点十条处置闭合(增益状态 v1.6 删「实装随该批」过时行/architecture v1.32 排雷规则件 preview_reveal 只读预览口登记+天赋树 tree_rules 参数字典 API 换代/奇物 v1.5 cap 必填与取值域写入/覆盖域表减动效行翻已覆盖;SPAWN_AREA_CELLS、shop.spend_discount 表字段、battle_driver 签名三处零登记零落点,天赋树 v0.12 等五份与术语表零反哺确认)、实机目检五项登记体验验证清单 ㊿〔实现报告原编㊾与 CI 迁远端批撞号,改用㊿〕、MC 锚代表性注记落 sim-data/README(重跑归一档用户发起)、产出物退役六件(三需求卡片+反哺点+实现报告+验收审计——吸收即死+证据只留最新)、待办清单三卡条目整条删除(架构审查余项〔审计待裁点④⑤⑥⑦〕按未决先迁再死另立新条登记)。迭代 49 收尾记录见 git 提交 aa396eb9,迭代 48 见 bd54d1f4。 -- **下一步**:迭代 51 已编入待开工(四卡 ≈6.625 工作日,`/iter 51` 发令)。待编池:改写族孤儿(待处置方向裁决)、冒险前资源配比预设(冻结备选)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 +- **迭代位**:迭代 1–51 全部已完成——迭代 51(四卡:EffectPort 蓄力层删键/架构审查 5.1 封装修复/P2 NODE_TYPES 词表单点化/L1 深度 3 混编)2026-11-04 合回(合并提交 fb27dc2f,独立验收审计终判通过),批后收尾闭合(2026-11-04):反哺点十条落文 architecture.md(第十一条落点不存在跳过、呈报在案)+S13 无凭证基线键经用户裁撤销(--refresh 重生成入口同批删除、基线改人工修订,gdstyle 红在案由还债 P0 追绿)+还债与死函数清退两条 P0 登记+presentation 卡未编迭代迁出承接条目+产出物退役三件+待办三条目删除。迭代 50 收尾记录见 git 提交 47c752ea,迭代 49 见 aa396eb9。远端 CI:run 51/52 红(runner 中断非闸红)、run 53–55 排队待 Mac 上线。 +- **下一步**:P0 两条待写卡(minesweeper_rules 公开面还债——gdstyle 红在案追绿;EffectEngine 死函数与全仓孤儿函数清退);presentation 层封装修复卡已写卡待编迭代(4.125 工作日)。待编池:改写族孤儿(待处置方向裁决)、冒险前资源配比预设(冻结备选)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 ## 里程碑时间线 @@ -84,7 +84,7 @@ | 迭代 48 | 已完成(2026-11-02) | 已退役(git 历史可考) | 2026-11-01 | ≈6.1 个工作日 | 已退役(git 历史可考) | | 迭代 49 | 已完成(2026-11-02) | 已退役(git 历史可考) | 2026-11-02 | ≈7.25 个工作日 | 已退役(git 历史可考) | | 迭代 50 | 已完成(2026-11-03) | 已退役(git 历史可考) | 2026-11-03 | ≈12.875 个工作日〔用户特批 2026-11-03 超 10 个工作日〕 | 已退役(git 历史可考) | -| 迭代 51 | 待开工 | reports/需求卡片-EffectPort蓄力层删键批-2026-11-04.md
reports/需求卡片-架构审查5.1封装修复批-2026-11-03.md
reports/需求卡片-架构审查P2代码面批-2026-11-03.md
reports/需求卡片-L1深度3混编-2026-11-04.md | 2026-11-04 | ≈6.625 个工作日 | — | +| 迭代 51 | 已完成(2026-11-04) | 已退役(git 历史可考) | 2026-11-04 | ≈6.625 个工作日 | 已退役(git 历史可考) | - 迭代 38 收口为带红合并〔用户特批 2026-10-26〕:09 静态扫描 S13 gdstyle 恶化 2 条经批准记入基线存量红(git 提交 c4c69701),卡② 停卡退上游,处方已于 2026-10-26 修订定稿 v1.3(git 提交 b3ea7532),随「gdstyle 存量债全量清偿」条目波 7 待编排。 - 迭代 39 收口为带红合并〔用户特批 2026-10-27〕:02 EV 锚 goblin 0.9148∉[1.00,1.20](触发器批有意行为改动,fp f74b9a48ef4a)已随 EV 锚重锚批换带归绿(git 提交 ba5105aa);08d 生涯.md:55 行号漂移已随 2026-10-28 批后收尾反哺落文修复归绿。三卡拆分后终局 gdstyle 恶化 2 条(battle_orchestrator max-file-length、level_map_screen max-class-variables)归迭代 40 卡①还债与迭代 41 存量债序列。 diff --git a/tools/gdstyle.toml b/tools/gdstyle.toml index 4f097cd7..d4466b5a 100644 --- a/tools/gdstyle.toml +++ b/tools/gdstyle.toml @@ -6,7 +6,7 @@ # INT64_MIN 负字面量是已知误报(Godot 自家解析器放行);type-hint 与 S9 类型闸重叠关; # empty-function 关(合法桩形态自由)。quality/no-debug-print 本是 opt-in 默认不跑,此处显式开 # ——产品码残留 print() 属真缺陷。阈值(函数长度/参数数等)取 gdstyle 默认值: -# 改阈值=基线口径变,须随批人工重生成基线。工具版本钉 v0.4.0(闸件校验,升级须重测兼容性)。 +# 改阈值=基线口径变,须随批人工修订基线(手动编辑 gdstyle_baseline.txt,经用户批准)。工具版本钉 v0.4.0(闸件校验,升级须重测兼容性)。 [rules] # ── 格式类全关(与 S9 类型闸/S10 文件头闸职能重叠)── diff --git a/tools/gdstyle_baseline.txt b/tools/gdstyle_baseline.txt index d1d6b45e..13b21725 100644 --- a/tools/gdstyle_baseline.txt +++ b/tools/gdstyle_baseline.txt @@ -3,11 +3,10 @@ # (class variables/public methods/file length)=文件|规则;无数字条目=文件|规则|消息全文(裸键)。 # 带数条目行格式「键 = 数值」,数值=消息剥掉 (max N) 段后首个整数;无数字条目裸键。 # 判定=键不在基线红(NEW)、同键数值较登记值恶化红(WORSENED)、改小/持平不红。 -# 重生成=人工专用:python tools/gdstyle_gate.py --refresh(禁清基线换绿;改规则口径/工具升级须随批 -# 重生成;新键数较旧基线少逾 10% 须加 --force 显式确认)。 +# 基线增删改=人工手动编辑本文件(闸件无重写基线入口;禁清基线换绿;登记值只准因还债下降, +# 禁抬高到现状;改规则口径/工具升级须随批人工修订并经用户批准)。 game/core/adventure_runner.gd|quality/max-class-variables = 23 game/core/level_runner.gd|quality/max-class-variables = 30 game/core/level_runner.gd|quality/max-public-methods = 33 -game/core/minesweeper_rules.gd|quality/max-public-methods = 21 game/presentation/views/board_view.gd|quality/max-class-variables = 31 game/presentation/views/board_view.gd|quality/max-public-methods = 27 diff --git a/tools/gdstyle_gate.py b/tools/gdstyle_gate.py index 49c375a5..9dffee18 100644 --- a/tools/gdstyle_gate.py +++ b/tools/gdstyle_gate.py @@ -22,19 +22,15 @@ quality/*+syntax/reserved-identifier,format/naming/order 全关)。扫描 阈值口径=gdstyle 默认值(tools/gdstyle.toml 不设数值阈值);基线只是欠账簿,登记值只准因 还债下降,禁抬高到现状(抬高=把既有代码当尺子,默认恶化)。 -基线重生成=人工专用(--refresh,附新旧对账、骤降守卫与抬升守卫),禁在闸内自动重生成 -(防清基线换绿);新键数较旧基线少逾 10% 拒绝写入(防扫面断裂静默缩基线换绿);任一键登记 -值被抬高同样拒绝(防以现状为尺吞掉恶化)。两条守卫都须 --force 显式确认,--force 只在 -refresh 路径有效。 +基线维护=人工手动编辑基线文件(本闸无任何重写基线的入口——防清基线换绿;增删行与改登记值 +都是人工动作,须用户批准;登记值只准因还债下降,禁抬高到现状)。 二进制:本机固定路径 D:/code/gdstyle/gdstyle.exe(不入库不入包),GDSTYLE_BIN 环境变量覆盖; -版本与钉定版不符即红——升级工具须重测语法兼容性并人工重生成基线。 +版本与钉定版不符即红——升级工具须重测语法兼容性并人工修订基线。 退出码:0=无基线外新违规且无恶化;1=有新违规/有恶化/二进制缺失或版本不符/扫描面为空/ -输出不可解析/refresh 骤降或抬升拒写;2=用法错。 +输出不可解析;2=用法错。 用法:python tools/gdstyle_gate.py # 闸判定 - python tools/gdstyle_gate.py --refresh # 人工重生成基线(覆盖旧文件,附对账) - python tools/gdstyle_gate.py --refresh --force # 骤降/抬升守卫触发时显式确认写入 """ import json import os @@ -53,8 +49,6 @@ PINNED_VERSION = "0.4.0" DEFAULT_BIN = ("D:/code/gdstyle/gdstyle.exe" if sys.platform == "win32" else "/usr/local/bin/gdstyle") MAX_REPORT = 50 -MAX_VANISHED_REPORT = 20 -SHRINK_GUARD = 0.9 # 新键数 < 旧键数 * 本系数=骤降,refresh 拒写须 --force _FUNC_RE = re.compile(r"function '([^']+)'") _MAX_RE = re.compile(r"\s*\(max \d+\)") @@ -112,31 +106,13 @@ def parse_baseline() -> dict: return entries -def write_baseline(entries: dict) -> None: - lines = [ - "# gdstyle 存量基线(S13)——键每行一条;只记基线不记豁免理由。", - "# 键形态(稳定身份键,计数漂移不换键):函数级带数条目=文件|规则|函数名;类级/文件级带数条目", - "# (class variables/public methods/file length)=文件|规则;无数字条目=文件|规则|消息全文(裸键)。", - "# 带数条目行格式「键 = 数值」,数值=消息剥掉 (max N) 段后首个整数;无数字条目裸键。", - "# 判定=键不在基线红(NEW)、同键数值较登记值恶化红(WORSENED)、改小/持平不红。", - "# 重生成=人工专用:python tools/gdstyle_gate.py --refresh(禁清基线换绿;改规则口径/工具升级须随批", - "# 重生成;新键数较旧基线少逾 10% 须加 --force 显式确认)。", - ] - for key in sorted(entries): - value = entries[key] - lines.append("%s = %d" % (key, value) if value is not None else key) - BASELINE.write_text("\n".join(lines) + "\n", encoding="utf-8") - - def run_gate() -> int: if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") args = sys.argv[1:] - refresh = "--refresh" in args - force = "--force" in args - if (force and not refresh) or any(a not in ("--refresh", "--force") for a in args): - print("[gdstyle_gate] 用法: python tools/gdstyle_gate.py [--refresh] [--force](--force 只在 refresh 路径有效)") + if args: + print("[gdstyle_gate] 用法: python tools/gdstyle_gate.py(无参数;基线维护=人工手动编辑基线文件)") return 2 binary = os.environ.get("GDSTYLE_BIN", DEFAULT_BIN) @@ -167,39 +143,9 @@ def run_gate() -> int: current = collect_entries(findings) - if refresh: - old = parse_baseline() if BASELINE.exists() else None - if old is not None: - vanished = sorted(set(old) - set(current)) - print("[gdstyle_gate] 对账:旧基线 %d 条,新扫描 %d 条,消失键 %d 条" - % (len(old), len(current), len(vanished))) - for k in vanished[:MAX_VANISHED_REPORT]: - print(" GONE " + k) - if len(vanished) > MAX_VANISHED_REPORT: - print(" ...(截断,共 %d 条)" % len(vanished)) - if len(current) < len(old) * SHRINK_GUARD and not force: - return fail("新键数 %d 较旧基线 %d 少逾 10%%——疑扫面断裂静默缩基线换绿,拒绝写入;" - "确认缩基线正当(如删规则/缩扫描面)须加 --force 显式确认" % (len(current), len(old))) - raised = sorted( - (k, old[k], current[k]) for k in current - if k in old and old[k] is not None and current[k] is not None and current[k] > old[k] - ) - if raised and not force: - for k, was, now in raised[:MAX_REPORT]: - print(" FAIL 抬高 %s = %d(登记 %d)" % (k, now, was)) - if len(raised) > MAX_REPORT: - print(" ...(截断,共 %d 条)" % len(raised)) - return fail("重生成会抬高 %d 条登记值——基线只准因还债下降,抬高=以现状为尺默认恶化;" - "确属阈值口径变更或经用户批准须加 --force 显式确认" % len(raised)) - else: - print("[gdstyle_gate] 旧基线不存在,跳过对账(首次落位)") - write_baseline(current) - print("[gdstyle_gate] 基线已重生成:%s 条(%s)" % (len(current), BASELINE.name)) - return 0 - base = parse_baseline() if BASELINE.exists() else {} if not base and not BASELINE.exists(): - return fail("基线文件不存在:%s(首次落位跑 python tools/gdstyle_gate.py --refresh,须经用户批准)" % BASELINE.name) + return fail("基线文件不存在:%s(首次落位=人工创建基线文件、格式见文件头注,须经用户批准)" % BASELINE.name) new = sorted(k for k in current if k not in base) worsened = sorted( @@ -222,7 +168,7 @@ def run_gate() -> int: print(" FAIL ...(截断,共 %d 条)" % len(worsened)) red = True if red: - return fail("基线外新违规 %d 条/恶化 %d 条——新码须修到基线口径内,恶化须还债或经用户批准随批重生成基线" + return fail("基线外新违规 %d 条/恶化 %d 条——新码须修到基线口径内,恶化须还债或经用户批准随批人工修订基线" % (len(new), len(worsened))) print("[gdstyle_gate] ok") return 0 diff --git a/待办清单.md b/待办清单.md index e6758455..b55eb019 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,22 +2,6 @@ 本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 -#### L1 深度 3 改地精+熊地精混编(大地精数据行退役)(2026-11-04 登记,来源=会话提及) - -**实况**:关联文档清单——[敌人系统](design/gdd/systems/战斗循环/敌人系统.md) v1.7(hobgoblin 缺口登记整块删除;敌种名录表已随工程批退役,行数据逐行读表)· [难度曲线与Boss](design/gdd/systems/冒险循环/难度曲线与Boss.md) v1.3(混编投放记录与 Alpha 读数义务口径)· [08-Alpha版本标准](design/gdd/08-Alpha版本标准.md) v1.26(混编遭遇行集状态行补第二行)· [流程规范](design/engineering/process-conventions.md) §3 第 5 条(Alpha 阶段零读数口径,2026-11-04 用户裁)· [体验验证清单](体验验证清单.md) ⑱(目检范围扩至 L1 节点 3)· 需求卡片 [需求卡片-L1深度3混编](reports/需求卡片-L1深度3混编-2026-11-04.md)(2026-11-04 写卡,八条验收断言 R1–R8、零待裁点)。事项来由与设计定值:用户 2026-11-04 裁不做大地精敌种,原「hobgoblin 内容三件套补齐」条目同批删除;(1, battle, 3) 改双行混编,行值整行复用 (1, battle, 2) 熊地精(1 只 / hp22 / ac2)与 (1, battle, 0) 地精(1–2 只 / hp8 / ac0),行序熊地精在前(默认单体目标与开战叙述取首行),静态最坏骰和 1+2=3,Alpha 阶段零读数。 - -**改动面**:文档+游戏代码+工具代码 - -**状态**:文档设计已闭合+已写卡+已编迭代 51 - -#### 架构审查余项——闸面缺口、豁免存废、零测试点名与架构文档漂移(2026-11-03 登记,来源=批次移交) - -**实况**:2026-10-29 全库架构审计(reports/审计-全库架构审查-2026-10-29.md)的待裁余项,自同名讨论条目迁入。现行:①闸面缺口(适用域落 code-conventions §4 第 13 条)②S13 豁免清偿(commit 9eedf896)③零测试点名(承接面落 test-conventions §8.3)三组随 §4 批落地(2026-11-03 用户裁决,CI 66407117 green);④架构文档漂移随 arch51-docs 批合回;§5.1 卡=reports/需求卡片-架构审查5.1封装修复批-2026-11-03.md v1.1(2.875 工作日);§5.3 卡=reports/需求卡片-架构审查P2代码面批-2026-11-03.md v1.1(0.25 工作日);EC8 热重载经用户裁撤卡(实装去向=另立条目);presentation 层私取面 2026-11-04 设计闭合并写卡(443 行/14 文件;用户两裁=三内部数据类字段公开化+四族一批)=reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md v1.0(4.125 工作日,凭据=reports/处置-presentation层封装修复-2026-11-04.md)。 - -**改动面**:文档+游戏代码+工具代码 - -**状态**:文档设计已闭合+已写卡+已编迭代 51 - #### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(2026-10-28 登记,来源=会话提及) **实况**:三关图类型色板(`tile_closed` 与 `wall_ambient` 两槽)终值冻结前须先跑「三关色相 × 五组类型微光」互辨抽测(格底与类型微光同在未开格面,邻近色相有吞没风险);美术设计段已闭合——取样面 54 格(3 关 × 5 组 × 3 底型+9 基准格)、判据=目检四条+机检四条(阈值全取在役值,色相通道不设机检阈值)、产物=并列样图+逐对读数表两兼。执行步骤与三件定值全载抽测方案过程稿 reports/提案-三关色相互辨抽测方案-2026-10-29.md;抽测挂点=[美术圣经](design/art/art-bible.md) v3.22 §6.3 终值取值程序第一条(终值冻结闸=§1.4 格子样张重跑)。执行前提已核:三关瓦片三套在本机 `game/assets/art/board/` 齐备,可开跑。 @@ -261,16 +245,6 @@ ### 文档与结构 -#### EffectPort biz_fact 键 charge_layers——无属主数据游离出领域实体的处置(2026-11-02 登记,来源=审计发现) - -**实况**:证据=reports/审计-全库架构审查-2026-10-29.md §3.1 B 类第 2 行+`game/core/effect_port.gd:4`(biz_fact 闭面五键)+`game/core/action_economy.gd:14/37/203`(裸账本与唯一写点)。2026-11-02 架构审查逐键核闭面:其余四键各有领域实体持有数据,唯 charge_layers 背后是无主账本(全仓零订阅注册、零表行消费);用户裁决=删除方向,总判据「机制数据一律长在所属封装实体的内部属性(表行字段)里,全局常量与无主账本清零」。需求卡片 reports/需求卡片-EffectPort蓄力层删键批-2026-11-04.md(v1.1——处置范围、残留面 15 处考证、断言换代申报与 grilling 四裁点落稿全载卡内)。 - -**2026-11-04 grilling 四裁(实现面按推荐案)**:①注释只清 charge_layers 关联语义(四处去退役名留机制描述),五拍其余退役名不动;②改写族孤儿另立条目(见下);③req-card 落「卡片落盘时点」条款;④编排凑批。纠错记录:v1.0 曾成文未复核态被误收口合回(错误在案);v1.1 新 worktree 落笔、成立裁决后才合回。 - -**改动面**:游戏代码 - -**状态**:已写卡+已编迭代 51 - #### damage_controller 按序改写族——内容孤儿机制与退役名注释层(2026-11-04 登记,来源=会话提及) **实况**:damage_controller.gd 按序改写族通用机制(trade_hit/trade_atk 交换、armor_rewrite 免掷、spike 满层必中与倍乘、discount_shift 轻甲改善——注释叙事 :266-273、改写执行 :283-309、spike 倍乘 :350-353)现役内容面仅淬毒刃纹一行 modify_ticket(mode=clamp_bonus,feats.tres:854):其余子分支零表行注册(全仓 grep 证,含天赋树 tree_rules 面零 spike)——「框架先行、内容未接」同族债,与 charge_layers 同形态但不持数据键。五拍注释全部以旧 27 专长名叙事(矿工重锤/专注/厚积薄发/猎弱本能,道具退役素材库.md:43-47 退役在案);其中 charge_layers 关联四处已归 EffectPort 蓄力层删键批卡处理,其余退役名注释不在该卡范围。证据:reports/需求卡片-EffectPort蓄力层删键批-2026-11-04.md 范围边界节(2026-11-04 grilling 复核发现)。 @@ -288,3 +262,27 @@ **改动面**:工具代码 **状态**:待执行 + +#### minesweeper_rules 公开面还债——S13 超标键撤销后的清偿(2026-11-04 登记,来源=批次移交,优先级=P0) + +**实况**:证据=tools/gdstyle_baseline.txt(本批撤销无凭证键后,gdstyle 闸红单键:game/core/minesweeper_rules.gd 公开方法 21>上限 20);+2 搬家方法 harmful_variants/harmful_adjacent 为迭代51卡② R2「五方法归位」裁决的直接后果(git 提交 7f512aa8),基点漂移考证:写卡基点 6261b36e 实数 18,iter50-卡3 a2886a53 +1 → 实现基点 19,+2=21。还债方向:settle_batch/fx_reveal 族收敛或搬迁,公开方法 21→≤20;还债批合回后 gdstyle 归绿(CI 追绿),期间代码域 CI 红在案、文档批不受影响(域分流)。 + +**改动面**:游戏代码 + +**状态**:待写卡 + +#### EffectEngine 死函数与全仓孤儿函数清退(2026-11-04 登记,来源=会话提及,优先级=P0) + +**实况**:证据=game/core/effect_engine.gd:204 has_effect_on 全仓零调用方(迭代51卡①删唯一调用方后成死码,grep 仅定义命中;卡① 备案见 git 提交 7f512aa8;2026-11-04 用户裁=删并全仓排查)。范围=删除 has_effect_on+全仓排查生产码零调用死函数/孤儿函数一并清退;测试直连与受控例外面不算死码,排查随写卡段考证。 + +**改动面**:游戏代码 + +**状态**:待写卡 + +#### 架构审查 presentation 层封装修复——已写卡待编迭代(2026-11-04 登记,来源=批次移交) + +**实况**:需求卡片 reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md v1.1(4.125 工作日;grilling 六项复核折入=git 提交 5fc9a131;用户两裁=三内部数据类字段公开化+四族一批;凭据=reports/处置-presentation层封装修复-2026-11-04.md)。443 行/14 文件四族(A1 数据类/A2 拆件子件/B 组合根成对转发梯/C 杂项)。自「架构审查余项」条目迁出(该条随迭代51完成整条删除,本卡未编迭代 51,由此条承接)。 + +**改动面**:游戏代码 + +**状态**:文档设计已闭合+已写卡 From 27f5e8b97e9aac734dd8fd983904b9ae4cf2824b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 06:54:06 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E6=96=87=E6=A1=A3(=E9=85=8D=E6=AF=94?= =?UTF-8?q?=E9=A2=84=E8=AE=BE=E8=BF=87=E7=A8=8B=E4=BB=B6=E9=80=80=E5=BD=B9?= =?UTF-8?q?):=20=E5=88=A0=E9=99=A4=20reports/=E9=9C=80=E6=B1=82=E5=8D=A1?= =?UTF-8?q?=E7=89=87-=E5=86=92=E9=99=A9=E5=89=8D=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E9=85=8D=E6=AF=94=E9=A2=84=E8=AE=BE=E6=89=B9-2026-10-04.md=20?= =?UTF-8?q?=E4=B8=8E=20reports/=E6=8F=90=E6=A1=88-=E5=86=92=E9=99=A9?= =?UTF-8?q?=E5=89=8D=E8=B5=84=E6=BA=90=E9=85=8D=E6=AF=94=E9=A2=84=E8=AE=BE?= =?UTF-8?q?-2026-10-04.md=E3=80=94=E7=94=A8=E6=88=B7=E7=89=B9=E6=89=B9=202?= =?UTF-8?q?026-11-04=20=E6=9A=82=E4=B8=8D=E5=81=9A=E3=80=81=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E7=95=99=E3=80=95=E2=80=94=E2=80=94=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E6=B8=85=E5=8D=95=E5=90=8C=E6=9D=A1=E7=9B=AE=E5=AE=9E?= =?UTF-8?q?=E5=86=B5=E5=8E=BB=E6=8E=89=E4=B8=A4=E4=BB=BD=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E4=BB=B6=E6=8C=87=E9=92=88=E3=80=81=E7=8A=B6=E6=80=81=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E3=80=8C=E6=96=87=E6=A1=A3=E8=AE=BE=E8=AE=A1=E5=B7=B2?= =?UTF-8?q?=E9=97=AD=E5=90=88=EF=BC=8B=E5=BE=85=E5=86=99=E5=8D=A1=EF=BC=88?= =?UTF-8?q?=E5=A4=87=E9=80=89=EF=BC=89=E3=80=8D=E5=B9=B6=E8=AE=B0=E3=80=94?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=89=B9=E6=89=B9=202026-11-04=20=E9=80=80?= =?UTF-8?q?=E5=BD=B9=E5=8D=A1=E7=89=87=E4=B8=8E=E6=8F=90=E6=A1=88=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E5=BE=85=E5=86=99=E5=8D=A1=E3=80=95=EF=BC=88=C2=A712.?= =?UTF-8?q?1=20=E4=B8=8B=E6=B8=B8=E6=AE=B5=E4=B8=8D=E5=9B=9E=E6=94=B6?= =?UTF-8?q?=E7=94=B1=E7=94=A8=E6=88=B7=E4=BB=A4=E4=BE=8B=E5=A4=96=EF=BC=89?= =?UTF-8?q?=EF=BC=9Broadmap=20=E5=BD=93=E5=89=8D=E4=BD=8D=E5=BE=85?= =?UTF-8?q?=E7=BC=96=E6=B1=A0=E5=88=A0=E8=AF=A5=E6=9D=A1=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E6=98=AF=E7=BC=96=E6=8E=92=E5=80=99=E9=80=89=E3=80=82?= =?UTF-8?q?=E4=BB=B6=E6=A0=B8=EF=BC=9Atracked=20=E9=9D=A2=E5=AF=B9?= =?UTF-8?q?=E4=B8=A4=E4=BB=BD=E6=96=87=E4=BB=B6=E9=9B=B6=E6=AE=8B=E7=95=99?= =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=88design/gdd/systems/=E8=B7=A8?= =?UTF-8?q?=E5=B1=80=E5=BE=AA=E7=8E=AF/=E9=94=BB=E9=80=A0=E5=B7=A5?= =?UTF-8?q?=E5=9D=8A.md:132=20=E5=8F=AA=E5=8F=99=E8=AF=A5=E6=89=B9?= =?UTF-8?q?=E6=8C=82=E8=B5=B7=E3=80=81=E4=B8=8D=E6=8C=87=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E5=8A=A8=EF=BC=89=E3=80=82=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=EF=BC=9Apipeline=5Fgate=20PASS(27=20=E6=9D=A1=E7=9B=AE/?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=8F=A3=E5=BE=84=207=20=E5=BC=A0)=E3=80=81r?= =?UTF-8?q?un=5Fall=5Fgates=20--docs=20=E9=AA=8C=E6=94=B6=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E9=9B=B6=E6=96=B0=E5=A2=9E=E7=BA=A2=E3=80=82=E5=BC=80?= =?UTF-8?q?=E6=89=B9=E6=97=B6=E6=9C=BA=EF=BC=9D=E7=94=A8=E6=88=B7=E4=BB=A4?= =?UTF-8?q?=E4=B8=8D=E7=AD=89=20CI=20=E5=85=A8=E7=BB=BF=EF=BC=88master=20?= =?UTF-8?q?=E5=9C=A8=E6=A1=88=E7=BA=A2=20run=2056=20gates/=E8=B7=91?= =?UTF-8?q?=E9=97=B8=20=E6=9C=AA=E4=BF=AE=EF=BC=8C=E5=BD=92=E5=9B=A0?= =?UTF-8?q?=E5=BE=85=E5=8F=96=20job=20=E6=97=A5=E5=BF=97=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reports/提案-冒险前资源配比预设-2026-10-04.md | 287 ------------------ ...œ€求卡片-冒险前资源配比预设批-2026-10-04.md | 99 ------ roadmap.md | 2 +- 待办清单.md | 4 +- 4 files changed, 3 insertions(+), 389 deletions(-) delete mode 100644 reports/提案-冒险前资源配比预设-2026-10-04.md delete mode 100644 reports/需求卡片-冒险前资源配比预设批-2026-10-04.md diff --git a/reports/提案-冒险前资源配比预设-2026-10-04.md b/reports/提案-冒险前资源配比预设-2026-10-04.md deleted file mode 100644 index 09330648..00000000 --- a/reports/提案-冒险前资源配比预设-2026-10-04.md +++ /dev/null @@ -1,287 +0,0 @@ -# 提案:冒险前资源配比预设(构筑系统)——立项前裁决调研 - -> 日期:2026-10-04 -> 性质:立项提案(纯调研,零代码,零 git 操作)——五个开放问题逐项列全备选案(含淘汰案与淘汰依据),各给一条推荐;全部拍板归用户(见 §八)〔2026-10-04 已全裁,裁决记录见 §九〕。 -> **⚠ 已退役存档(2026-10-18 用户令入库)**:八裁与盘问四裁全落后,本提案已由[需求卡片-冒险前资源配比预设批-2026-10-04](需求卡片-冒险前资源配比预设批-2026-10-04.md)吸收(吸收即死)——裁决结论唯一事实源=06-待办清单「冒险前资源配比预设(构筑提案)」条+该卡片;本文件只作推导过程与淘汰依据的历史档,重启该批勿以本文为现行口径。 -> 事实源:[06-待办清单](../待办清单.md)「冒险前资源配比预设(构筑提案)」条(2026-10-04 现行态)· [元进度](../design/gdd/systems/跨局循环/元进度.md) v1.6 · [地图战斗资源](../design/gdd/systems/战斗循环/地图战斗资源.md) v0.16 · [难度曲线与Boss](../design/gdd/systems/冒险循环/难度曲线与Boss.md)(带换代节)· [关卡节点地图](../design/gdd/systems/冒险循环/关卡节点地图.md) v1.5 · [棋盘生成](../design/gdd/systems/排雷循环/棋盘生成.md) v0.4 · `game/data/tables/reward.tres`(现行权重逐行核证)· 姊妹研究 方案-挖掘风险不等式修订-2026-10-04(已退役,git 历史可考) · 外部先例出处随文注(§二与附录)。 - ---- - -## 一、提案摘要与现状事实底座 - -### 1.1 核心想法(06 条目在册口径的复述) - -玩家在**开始冒险前**预设格面资源的类型配比——预设的是**分布**(各型在格面池里的权重占比),不是落位:装板摇号仍按专属随机流摇出各格类型身份,玩家定分布、摇号定落位。雷安同池 + 极性矩阵使配比自带代价轴:调高某型权重,安全格正向与雷格负向同步变多——配比 =「赌自己挖掘少失误 + 赌关卡敌人构成怕什么」的双重下注(06 条目原文口径),不是免费午餐。 - -### 1.2 写作时核证的现状事实(提案的底座,全部有出处) - -| # | 事实 | 出处与口径 | -|---|---|---| -| 1 | **格面池 = 九型 77 权重**:attack 14 / defense 11 / blast 16 / heal 4 / weaken 8 / pierce 8 / magic 8 / action 6 / gp 2,合计 77 | `reward.tres` 逐行核证(与姊妹研究 §2.3 核证一致)。**06 条目写「八型」系条目登记时 gp 型未入池的口径差——立项裁决须先收口为九型口径**(见 §八末注记) | -| 2 | **权重注入的代码先例已存在**:同调被动件「偏运硬币」装配时对装板摇号注入池内权重加算(稀有正向四型 weight +N),未装配时逐位返回原行集(零扰动),池成员集合与取号次数零变化 | `reward_economy.gd` `_biased_cell_rows()`(v0.16 注记;`luck_bias=0` 即 off 态零扰动)。配比的注入通道可走同一合成层,零新机制 | -| 3 | **元进度参数面已登记双语义**:数值覆盖(属性锻造 flat 加算)+ 集合操作(追加/替换,道具池/遭遇/奖励格预留) | 元进度 v1.0「跨局注入」节;`start_adventure params.hero_override → LevelRunner base_override` 通道已落地(v1.3) | -| 4 | **按格型差异化已否决**:A3「雷格与安全格的资源分布差异化」2026-10-04 用户裁「不做了、想到别的玩法」,06 条目整条移除 | active.md 台账 328 ②。本提案的配比是**整体平移分布**(雷安仍同池同分布),与 A3 无冲突;但 A3 否决给问题⑤立了红线(§7.1) | -| 5 | **奖励格图鉴柜台空置**:其原定内容(特殊奖励格)的载体 = 地图战斗资源差异化批,随 A3 否决失载体,柜台现为无内容结构位 | 元进度「营地三柜台」节 + v1.6(道具工坊已改写为同调装配,三柜台中仅奖励格图鉴无正业) | -| 6 | **验收带 = 外部类型基准锚定四带**:带 A 白板全通 [0,10] / 带 B 白板首关 [50,85] / 带 C 营地满成长全通 [30,60](预立)/ 带 D 响应曲线形状(每雷边际 ≤15pp、S 形、无 >25pp 单步跳变) | 难度曲线与Boss「带换代」节(2026-10-03 用户拍板;外部锚点 = 尖塔 9% 全通 / FTL 9.5% / 死亡细胞首 Boss 72.9% 等) | -| 7 | **双重下注的代价轴已有量化起点**:姊妹研究算出逐型条件净期望——pierce 型 −1.25 / magic 型 −0.50 当量·格(名义失误率下为负),直伤三型占权重 26%;即拉高穿刺/魔法 = 同步抬高最贵惩罚档(pierce_snare / magic_recoil 直伤档)的出现频次 | 方案-挖掘风险不等式修订 §四(形态〔a〕δ=1.25 已随迭代 14 ②号卡裁决入册)。「配比不是免费午餐」不是修辞,是已量化的账 | -| 8 | **节点渠道现状**:节点奖励 = 提升三选一令牌池(五种 + 档位,与商店共享池);魔法道具 = 局外同调件(aux 渠道 2026-10-03 退役);地图战斗资源 P1 仍留「功能道具渠道」结构位 | 关卡节点地图 v1.4/v1.5 + 地图战斗资源 P1 表 | - -### 1.3 一句话结论(预告,展开在各节) - -预算机制推荐「**总量挪点 + 每型包络 + 容量解锁渐进**」三层组合(§三);主权切分推荐「设计者定包络与默认、玩家在包络内分配;验收 = 默认分布锚不动 + 包络极端构筑护栏」(§四);容量柜台推荐「奖励格图鉴承接」(§五);注入语义推荐「差值向量存档 + 摇号前权重合成层单点合成」(§六);边界推荐「清图槽差异化先行 + 三句不混池口径立法」(§七)。 - ---- - -## 二、外部先例盘点(出处注明) - -本仓元进度定稿批(active.md 台账 66)曾就营地/跨局结构做过行业调研(Hades 夜之镜 / Dead Cells 收藏家 / Loop Hero 营地 / Rogue Legacy 庄园),本节在其基础上聚焦「**开局前构筑 / 池构成控制**」这一个切面。 - -| 先例 | 机制要点(与本提案相关的) | 对本提案的启示 | -|---|---|---| -| **Loop Hero**(Four Quarters, 2021):卡组系统 | 玩家在局外从已解锁地形卡里编卡组(预设**分布**);局内抽到卡后自选**落位**;每卡有复制上限;金卡全组仅一;新卡靠营地建筑(元进度)解锁 | **最同构先例**:「预设分布不预设落位」已被验证可玩;营地解锁新卡 = 配比容量当解锁内容的直接样板 | -| **Hades 夜之镜**(Supergiant Games, 2020) | 黑暗点数做预算,逐槽投入;多数槽位是设计者策展的**二选一**(如 Greater Reflex vs Ruthless Reflex,变体花钥匙解锁、可随时切换);无全量洗点(投入即沉淀) | 点数预算 + 设计者策展选项 = 「主权切分」的样板:玩家有点的自由,没有越过槽位设计的自由 | -| **Dead Cells 自定义模式**(Motion Twin / Evil Empire, 2018 起) | 玩家可筛选道具池构成,但**筛选后可用装备少于 20 件即锁定成就与 Boss 细胞掉落**;个别标记选项(如全无色化)也锁 | 「包络内自由、越包络即出局」的测量保护实锤:玩家可以调池,但调到破坏测量基线的程度就被沙箱化,不计入正式进度 | -| **Risk of Rain 2 指令神(Artifact of Command)**(Hopoo Games, 2020) | 开箱改为三类别自选道具 = 事实上的**无约束自由分配**;社群公认让局失去张力,普遍自律不用 | 自由滑条路线的活体反例:无预算无上限的分配 = 支配策略实锤,与 06 条目「自由滑条必出支配策略」的预判互证 | -| **杀戮尖塔**(Mega Crit, 2019):开局与进阶 | 开局 Neow 奖励 = 四选一(策展选项非自由分配);进阶等级 = 设计者包络逐级收紧;遗物按 common/uncommon/rare/boss/shop 分池调度并带保底 | 全程回避自由滑条:开局构筑走策展、难度主权在设计者——「玩家主权完全数值化」的对照系 | -| **桌面战棋军表编制**(战锤 40K 兵力编制组织图 / D&D 点数购点,桌上通识) | 总点数预算 + 每类别数量上下限(如重型支援 0–2、部队 2–6)+ 总分上限三层叠加,几十年演化的成熟答案 | 预算 + 每型上限**组合使用**是杀支配军表的行业标准;单用任一层都有已知漏洞(见 §3.2 案 D 分析) | - -出处补充:Dead Cells 20 件下限 = 官方补丁与社群共识(Reddit r/deadcells 成就改动讨论帖);Hades 双选项解锁 = Hades Fandom Wiki「Mirror of Night」条目;Loop Hero 卡组/营地解锁 = 官方玩法说明与 Kakuchopurei 指南;RoR2 指令神 = 官方 Wiki 条目。检索日期均为 2026-10-04。 - ---- - -## 三、问题①:预算机制(最大设计题) - -### 3.1 问题本质:自由滑条死在哪里 - -06 条目已判:自由滑条必出支配策略——全拉穿刺 + 魔法(两型都是必中克制直伤,战斗侧收益最硬)大概率唯一解,构筑空间反死。展开成三条死因: - -1. **数值死因(唯一解)**:无总量约束时,玩家把点数全压战斗期望最高的两三型。代价轴(姊妹研究 §四:pierce/magic 逐型净期望为负)只防「免费午餐」,不防「最优解」——若战斗侧收益 > 挖掘侧代价(对低失误率玩家很可能成立),全押仍是理性唯一解。 -2. **验收死因(测量不可收敛)**:连续权重空间里每个分布都要测通关率带,读数维度爆炸;对照 Dead Cells 的做法,无包络的池控制直接不被测量体系接纳。 -3. **对照死因(先例反证)**:RoR2 指令神即无约束分配的成品反例;杀戮尖塔与 Hades 全程不给自由滑条。 - -### 3.2 备选案全列 - -**案 A:自由滑条(无预算无上限)**——玩家对九型权重任意拉。 -淘汰。依据 = 上节三条死因 + 06 条目既判。列出仅为闭环。 - -**案 B:点数预算·加点制**(默认分布之上只加不减)——开局给 K 点,每点使某型权重 +1,不可下调;分布总和从 77 涨到 77+K。 -- 优点:每点 = 严格增量,逐点归因干净——换算率表(每级带平移量,现行生命 6 / 攻击 9 / 护甲 1 三行同构)直接增行承接,验收纪律现成;白板语义最干净(零加点 = 零扰动)。 -- 缺点:**每点都是纯变强**——容量等级 = 力量等级,元进度变成第二属性锻造;且「全加伤害型」在每型上限内仍可集中(加点制不逼取舍,只加税),支配策略风险靠上限单防;类型频次合计漂移(77+K),行动经济对账的原材料总量随容量变化,对账窗口须逐容量档复算。 -- 结论:**正经备选,非淘汰案**——若用户更看重验收逐点归因与柜台语言统一,此案可行。 - -**案 C:点数预算·总量挪点制**(总和恒定)——九型权重总和恒为 77,玩家把点数从一型挪到另一型,每型只能在包络区间 [下限, 上限] 内动;默认分布本身是一个合法配置(全零挪动 = 现行分布)。 -- 优点:**零和 = 真取舍**——拉高穿刺魔法必须以压低别的型为代价;且算术上「全押两型」不可达(例:若包络为每型 [2, 20],穿刺魔法双双顶满 40 点后,剩余 37 点必须落在其余七型里,玩家被迫留续航面)。类型频次合计 77 恒定 → 行动经济对账与既有模拟锚的**总量面零漂移**,验收只剩分布位移一个新变量。双重下注的完整形态天然在场:压低某型 = 同时躲开它的正向与负向(「我不采它的奖励也躲它的惩罚」本身是配比博弈的一部分)。 -- 缺点:容量等级不再产生「带平移量」(挪点不加力),换算率表接口要换形态(见 §4.4);「挪点」比「加点」的理解成本略高;若下限允许 0,续航型可被清零(须下限 > 0 保底,见子裁 3)。 -- 结论:**推荐主案**(理由收在 §3.3)。 - -**案 D:每型上限(无总预算)**——每型独立在 [下限, 上限] 内自由调,无总量约束。 -淘汰**单用**,保留为组件。单用漏洞:各型上限独立时,玩家可同时顶满三个伤害型 + 全部压到下限——06 担心的唯一解在多数包络设定下仍可达(上限只防单型独大,不防多型合围)。作为案 B/C 的组成件(包络区间)则是必需品——桌面战棋的类别上下限正是与总点数**组合**使用的。 - -**案 E:模板/槽位内容化**——设计者策展成套预设(如「均衡 / 爆发 / 控制 / 贪婪」四套分布),玩家只选不调;或可调型槽位本身是解锁内容(初始只有 2 型可调,逐级解锁)。 -- 优点:策展杀支配策略最彻底(每套模板都经设计者验算);杀戮尖塔开局奖励 / Hades 武器形态路线同款;「槽位当解锁内容」有 Loop Hero 直接样板。 -- 缺点:纯模板形态玩家作者性最弱(选套餐不是构筑);模板内一旦证明某套最优,支配策略只是换了持有人(策展内唯一解)。 -- 结论:**正经备选**;且其「解锁内容化」半边是组合案的必需组件。 - -**案 F:组合制(挪点 + 包络 + 解锁渐进三层叠加)**——案 C 的总量挪点为骨架,案 D 的每型包络为约束,案 E 的容量解锁为渐进排布(初始锁定默认分布,逐级开放:可调型数 → 包络宽度 → 预设档位数)。 -这正是桌面战棋(总点 + 类别上下限)与 Loop Hero(卡组容量随营地长大)的共同形态,也是 Hades(点数预算 + 槽位策展)的变体。 - -### 3.3 推荐 - -**案 F(以案 C 为骨架的组合制)**。理由: - -1. 唯一解被**结构性**约束(挪点逼取舍 + 包络封顶 + 下限保底),不是靠数值祈祷——姊妹研究已给出代价轴量化,包络只需封住尾部极端; -2. 类型频次合计 77 恒定,行动经济对账、既有模拟锚的总量面零漂移——五个问题里验收成本最低的形态(§四展开); -3. 白板成立纪律自动满足:默认分布 = 合法配置之一,零配比存档与现行逐位一致(偏运硬币 off 态零扰动的同款保证); -4. 容量解锁渐进给元进度一个**不加力的成长钩子**(开放的是自由度不是数值——与「单局循环零依赖」硬边界相容性最好)。 - -若用户更重视「每级解锁 = 每行换算登记」的既有纪律亲和,**案 B 是合格的第一备选**(加点制逐点归因最干净),代价是容量=力量与总量漂移两处。 - -**随本案的子裁三件(小口径,随批一并拍)**: -- 子裁 1:**gp 型是否进可调集**。推荐**首发不进**(gp 定位「低概率惊喜获取」权重 2;拉高 gp = 金币泵直通商店成长,经济侧杠杆宜归设计者)。口径:gp 的包络 = [2,2](恒定),后续随内容批再开放。 -- 子裁 2:**预设档位数**(玩家可存几套命名预设)。推荐 3 套(足够「读敌换阵」轮换,存档域小)。 -- 子裁 3:**下限是否 > 0**。推荐 > 0(如统一 2 或按型定),保证每型正向与负向都有最低出场频次——雷安同池的代价轴依赖「每型都可能出现」这一前提。 - ---- - -## 四、问题②:杠杆主权切分 - -### 4.1 问题拆成两问 - -06 条目原文:「切片与验收期锁默认权重(测量保护),正式期『设计者定预算包络、玩家分配』的参数归属」。即:**正式期参数归谁** + **验收期怎么测**。 - -### 4.2 正式期参数归属备选 - -| 案 | 内容 | 判 | -|---|---|---| -| 甲 | **设计者定包络与默认,玩家在包络内分配**:包络区间、总量、默认分布、解锁排布归设计者;分配权归玩家 | **推荐**。Hades(槽位策展 + 点数自由)/ Dead Cells(池可筛但有下限)/ 桌面战棋(编制图 + 军表自由)三者共同形态;玩家主权的边界 = 测量体系的边界,两界重合 | -| 乙 | 玩家全权(包络无界) | 淘汰 = §3.2 案 A 同一条死路(验收不可收敛) | -| 丙 | 设计者定全套预设、玩家只选不调(纯策展制) | 正经备选(= §3.2 案 E 纯形态):测量最干净、作者性最弱;若用户想先低风险上线构筑感、后开自由度,此案是渐进路线的**第一阶段**——与案 F 的解锁渐进天然衔接(先解锁模板、后解锁自由调) | - -### 4.3 验收期「锁默认权重」口径备选 - -| 案 | 内容 | 判 | -|---|---|---| -| 甲 | **全锁**:一切验收读数(带 A/B/C/D)一律默认分布跑,配比构筑完全不进测量 | 省事但有盲区:配比若能把带打穿(包络定松了)测不出来——「上线即越带、无人看见」 | -| 乙 | **默认锚 + 极端构筑护栏**:默认分布 = 现行四带正主读数不动(一切既有锚复用);另加一条**构筑护栏带**——把包络推到极限的极端配比(每型取上/下限的最端组合,取「最大化直伤型」「最大化续航型」「最大化经济型」等几个代表角)与默认分布的带差设上限,类比带 D(形状带)对带 A/B/C 的护栏关系 | **推荐**。Dead Cells「20 件下限」同思路的验收版:自由可以被测量,越界会被看见;极端角数量有限(取代表角即可),测量可收敛 | -| 丙 | 全开(每次读数带玩家分布) | 淘汰:分布空间组合爆炸,测量不收敛(§3.1 死因 2 的验收版) | - -### 4.4 换算率表接口(随 §三选择联动的自动推论) - -- 若 §三选**案 B(加点制)**:容量每级 = 换算率表新行(与生命 6 / 攻击 9 / 护甲 1 现行三行同构,逐级实测投值)——接口最亲。 -- 若 §三选**案 C/F(挪点制)**:容量等级**不加力**,换算率表不增行;力量的度量转入 §4.3 案乙的「构筑护栏带」(极端构筑 vs 默认的带差上限,一条带管全部容量档)。 -两条路线都满足「每次上架新商品伴随一行登记」的元进度纪律——只是登记的表不同。 - -### 4.5 切片与对拍纪律(随批义务,非拍板点) - -- 冒险开始参数面携带配比差值(§六),切片对拍须含「配比 off 态逐位不变」断言(偏运硬币 off 态同款);配比 on 态按代表角加对拍组。 -- 同种子确定性:同种子 + 同预设 = 同一张身份表(专属随机流消费纪律不变——取号次数只随非封锁格数,不随权重)。 - ---- - -## 五、问题③:配比容量映射营地三柜台 - -### 5.1 三柜台现状(元进度 v1.6 时点) - -| 柜台 | 现状 | 与配比容量的语言相容性 | -|---|---|---| -| 属性锻造 | 三档 flat 属性行(生命/攻击/护甲)+ 换算率表三行;投值语言锁定 **flat 整数加减** | 低——配比容量是分布参数不是属性值,投值语言与换算率行语义都不同构 | -| 道具工坊 | 职责已改写为**同调装配**(谓词表 + 装配入口 + 刷新升级行,v1.6) | 中——构筑哲学同源(装配/换装),但柜台职责刚改写完,再叠配比即一个柜台两份职责 | -| 奖励格图鉴 | **空置结构位**:原定内容(特殊奖励格)载体 = 差异化批,随 A3 否决失载体(§1.2 事实 5) | 高——域亲和(格面池内容的柜台)+ 空置等活 | - -### 5.2 备选案全列 - -- **案甲:奖励格图鉴承接**。配比容量(可调型数 / 包络宽度 / 预设档位解锁)全部入此柜台。 - 优点:给空置柜台第一个正业;域亲和最高(格面资源域的跨局内容本就该住这);柜台名「图鉴」语义随批扩写为「格面构筑」(命名微调走元进度修订)。 - 代价:柜台语义改写 = 元进度文档修订一处;原「特殊奖励格」内容仍悬置(其死活是独立问题,不因配比入驻而销号)。 -- **案乙:道具工坊(同调装配段)并入**。构筑类内容归一处。 - 淘汰(首发):v1.6 刚把工坊职责改写为魔法道具同调,再叠配比 = 职责二混;且同调件是「件」(装配关系入存档、冒险中途不可换装),配比是「参数面开局注入」——生命周期与存档域都不同。 -- **案丙:属性锻造加行**。容量当第四条属性行。 - 淘汰:投值语言锁定(flat 整数加减)与容量语义不通(容量不是数值行,是权限/区间参数);换算率行语义不同构(§4.4 挪点制下容量根本不产生带平移)。 -- **案丁:第四柜台(构筑台)**。三柜台封闭枚举扩四。 - 正经备选但后置:柜台扩编牵动营地 UI(三柜台列视觉终稿已落码)、存档域、呈现面全链;仅当配比内容量大到独立成店(模板 + 自由调 + 档位管理一整套)才值。首发用案甲成本减半,将来量大了再迁柜(迁移 = 柜台枚举修订一次)。 -- **案戊:零柜台(开局全量可用,无解锁)**。配比能力不进元进度,设置面开局即用。 - 淘汰(与 06 方向冲突但列全):06 条目已登记「可用类型 / 预算点数 / 预设槽位走营地柜台解锁」的方向;且零门槛起步 = 九维构筑界面直接砸向新玩家(教学成本转移),白板存档虽然仍成立(默认分布在),但「第一局的最优体验是别碰这个界面」不是好默认。 - -### 5.3 推荐 - -**案甲(奖励格图鉴承接)**,配 §三案 F 的渐进排布:第一级解锁先给「两型可调 + 窄包络 + 1 档位」,逐级开放到全量;定价走柜台既有几何递增原则(起步价与公比归元进度设计批配读数)。戊案「开局即有默认分布可用」的白板精神已含在案 F 里(零配比 = 现行分布),无需额外机制。 - ---- - -## 六、问题④:权重覆盖注入语义 - -### 6.1 现状:两条既有通道(§1.2 事实 2/3) - -1. **元进度参数面**(`start_adventure params.hero_override → LevelRunner base_override`):开局一次注入,登记了「数值覆盖 + 集合操作(追加/替换)」双语义——但它是**主角数值**的通道。 -2. **偏运硬币合成层**(`begin_battle luck_bias → reward_economy._biased_cell_rows`):摇号前的池内权重**加算**层,off 态零扰动——这是离配比最近的一条(配比改的正是摇号权重表)。 - -### 6.2 备选案全列 - -| 案 | 存档与注入语义 | 判 | -|---|---|---| -| 甲 | **绝对权重覆盖**:存档直接存每型权重的绝对值(如 attack=18),摇号用存档表 | 淘汰为主、降级保留:直观,但设计者再平衡不传播——默认权重将来调整时,老存档钉死旧绝对值,逐档漂移;对拍断言也要按存档绝对值写,失「off 态逐位不变」保证 | -| 乙 | **差值向量**:存档只存「相对默认的偏移」(每型 Δw,挪点制下 ΣΔ=0),摇号前合成「表值 + 同调偏置 + 配比差值 → 包络钳制」 | **推荐**:设计者再平衡自动传播(玩家意图 = 相对默认的偏好,默认动则偏好跟着动);off 态(全零向量)= 逐位原样,切片断言干净;存档域小(九个整数 + 档位元数据);与偏运硬币同一合成层可组合(§6.3) | -| 丙 | **集合操作(追加/替换)**:按元进度参数面为池预留的语义套用 | 降级为辅语言:配比本体是**九型封闭枚举上的权重数值问题**——池成员恒同(九型全在池),集合语义与本体不匹配。但「可调型集」的解锁扩容(初始 2 型可调 → 逐级追加)天然是**追加**语义——集合语义保留在解锁层使用,与参数面「属性=数值、池=集合」的既有分工严丝合缝 | -| 丁 | 乘算/比例系数(每型 ×k) | 淘汰:推理难(×1.3 的直觉差)、代码零先例、验收归因难(乘算效果随表值漂移);无增益 | - -### 6.3 实现注记(零新机制口径的落实位,非拍板点) - -- **合成单点**:`reward_economy` 的 `_biased_cell_rows()` 是现成的「摇号前权重合成层」——扩为多生产者合成(表值 → 同调偏置 → 配比差值 → 包络钳制 → 加权抽取)即承接 06 条目「架构红利①:注入通道零新机制」的承诺;或旁挂一个同构函数,二择一归实施批,语义不变。 -- **确定性纪律**:专属随机流(reward_roll)的取号次数只由非封锁格数决定,权重只改每一次抽取的分布——同种子同预设必出同一张身份表;配比 off(全零差值)必逐位等于现行结果。 -- **参数面登记义务**:冒险开始参数面新增字段 = 配比差值向量(或「预设档引用 + 档内差值」两段式)+ 容量等级随档;字段语义登记归冒险结构修订批随批(06 影响面已列)。 - ---- - -## 七、问题⑤:关卡特殊资源与节点功能道具渠道的边界 - -### 7.1 现状与三条既有红线 - -1. **A3 红线**:按格型(雷格/安全格)差异化已被用户 2026-10-04 否决注销(「不做了」)——关卡特殊资源的任何形态都不得按格型分,否则 A3 走后门还魂。 -2. **结构冻结面**:九型封闭枚举、雷安同池(同池同分布)、装板期一次摇定无重摇、清图恒非空——关卡特殊资源的载体选择只能在冻结面之外找座位。 -3. **渠道现状**:节点奖励 = 提升三选一令牌池;商店 = 共享池三选一同构卡;魔法道具 = 局外同调件(节点 aux 渠道已退役);地图战斗资源 P1 仍留「功能道具渠道」结构位(长周期功能性道具经其他渠道加入,渠道设计未定)。 - -### 7.2 备选案全列(关卡特殊资源的承载形态) - -| 案 | 形态 | 判 | -|---|---|---| -| 甲 | **清图槽差异化先行**:只启用 `clear_<图类型>` 预留插槽(词表 {dungeon, underdark, deep_underdark} 已定稿,L1/L2/L3 各一主题)——不同关卡清图奖励不同,格面池零触碰 | **推荐首选**:结构位已留、零格面池改动、零雷安面改动、零枚举扩;差异化叙事(不同主题关给不同清图对象)最小成本起步 | -| 乙 | **格面池关卡专行**:某关图主题在九型之外带独占行/独占型(如幽暗地域独有毒系格面) | 后置正经备选:扩九型封闭枚举 = 修订流程重走(八型批/九型批同流程);且与玩家配比共用权重面,必须先立「专行权重归设计者、不入可调集」的隔离口径(正好由 §7.3 口径 2 承接);排 Alpha 内容批 | -| 丙 | **分布参数差异化**:关卡对基础九型权重表加设计者偏置(如某关整体偏削弱/护甲) | 淘汰(首发不做):与玩家配比是**同一个操作面**(权重平移),两条生产者挤一个旋钮——所有权混淆 + 合成叠序复杂;「关卡差异」用敌人构成(敌人系统已有混编遭遇行集)与清图槽(案甲)表达足够 | -| 丁 | **特殊格保底位**:每图保证 N 个九型之外的特殊格(摇号清单外加席) | 淘汰:同时触碰雷安同池、摇号纪律、封闭枚举三冻结面,且「按席保底」离「按格型差异化」只有一步——A3 邻域,红线风险最高 | - -### 7.3 不混池口径(三句立法草案,随批入 GDD) - -1. **板图分工句**:效应在板上,物件在图上或局外——格面资源与清图对象是战斗内即时兑付的效应(无库存、无道具化);持久性功能物件只走节点渠道(三选一/商店)或局外同调。任何内容只走一条渠道,同一物禁双渠道出现。 -2. **配比域封闭句**:玩家配比只作用于基础九型格面池的权重;关卡特殊资源(清图槽差异化、未来专行)的权重归设计者,永不入可调集。 -3. **格型无差句**:一切关卡差异化按图主题(board_type 词表)分,不按格型(雷格/安全格)分——A3 否决边界沿用,写进两条系统文档的纪律表。 - -### 7.4 推荐 - -**案甲(清图槽差异化先行)+ §7.3 三句口径入文**。案乙留作 Alpha 内容批的正餐;案丙丁出局。 - ---- - -## 八、拍板点清单(全部归用户) - -| # | 拍板点 | 备选 | 推荐 | -|---|---|---|---| -| P1 | 预算机制 | 案 A 自由滑条(淘汰)/ 案 B 加点制 / 案 C 挪点制 / 案 D 每型上限(单用淘汰,组件保留)/ 案 E 模板·槽位内容化 / 案 F 组合制 | **案 F(C 骨架 + D 包络 + E 渐进)**;B 为第一备选 | -| P1-子 | gp 型可调与否 / 预设档位数 / 下限是否 >0 | — | 首发不可调 / 3 套 / 下限 >0 | -| P2 | 主权切分(正式期) | 甲 包络内分配 / 乙 玩家全权(淘汰)/ 丙 纯策展 | **甲**(丙可作为解锁渐进的第一阶段) | -| P2-子 | 验收口径 | 甲 全锁 / 乙 默认锚+极端构筑护栏 / 丙 全开(淘汰) | **乙** | -| P3 | 容量柜台 | 甲 奖励格图鉴 / 乙 道具工坊(淘汰)/ 丙 属性锻造(淘汰)/ 丁 第四柜台(后置)/ 戊 零柜台(淘汰) | **甲** | -| P4 | 注入语义 | 甲 绝对覆盖(淘汰)/ 乙 差值向量 / 丙 集合操作(降级为解锁层辅语言)/ 丁 乘算(淘汰) | **乙** | -| P5 | 边界口径 | 甲 清图槽先行 / 乙 格面池专行(后置)/ 丙 分布偏置(淘汰)/ 丁 保底特殊格(淘汰) | **甲 + 三句口径** | - -**随批口径收口注记**:06 条目「八型格面池(…action 行动点)」系条目登记时 gp 型未入池的旧口径;现行格面池 = 九型(gp 已入池,权重 2)。本提案全篇按九型行文;06 条目修订随立项批一并收口。 - -**前置复述(06 条目在册,未动)**:① 验收批读数重测与真人试玩先跑——配比博弈的核心未知数(玩家配比是否收敛到唯一解、分布先验怎么被利用)只有真人数据能回答;② 当前批序列走完(本批审批 → 验收重测 → 曲线调参 → 魔法道具内容批)。实施窗口 = Alpha。 - ---- - -## 九、裁决记录(2026-10-04,全落) - -用户令「拍板点全部使用推荐」——§八 全部拍板点按推荐案落定: - -| # | 拍板点 | 裁决(2026-10-04) | -|---|---|---| -| P1 | 预算机制 | **案 F 组合制**——总量挪点为骨架(九型权重总和恒 77)+ 每型上下限包络 + 容量解锁渐进;子裁三件随裁:gp 型首发不可调(包络 [2,2] 恒定)/ 预设档位 3 套 / 下限 >0 | -| P2 | 主权切分 | 正式期 = **案甲**(设计者定包络与默认、玩家包络内分配);验收口径 = **案乙**(默认分布 = 现行四带正主读数不动 + 新增「极端构筑护栏带」管包络代表角) | -| P3 | 容量柜台 | **案甲 奖励格图鉴承接**(柜台语义随批扩写;原「特殊奖励格」内容的悬置问题独立存在,不因本裁销号) | -| P4 | 注入语义 | **案乙 差值向量**(存档存相对默认偏移 ΣΔ=0,摇号前合成层单点合成);集合操作保留在解锁层(可调型集扩容)作辅语义 | -| P5 | 渠道边界 | **案甲 清图槽差异化先行 + §7.3 三句不混池口径随批入文**(板图分工 / 配比域封闭 / 格型无差) | - -连带收口:06 条目「八型格面池」旧口径随本批改九型(gp 已入池,正向权重合计 77)。 - -后续:① 前置不变(06 在册)——真人试玩读数先跑 + 当前批序列走完,实施窗口 = Alpha;② 需求卡片随实施窗口起草,本提案自裁决日起转裁决事实源;③ 本文件未跟踪落盘,入库与否归用户。 - ---- - -## 十、盘问轮四裁(2026-10-04,grill-me 轮——灰区与遗漏补裁,全按推荐落定)+ 编卡 - -立项五裁(§九)之后盘问轮扫出四处灰区与六项后置,四灰区当日全裁: - -| # | 问题 | 裁决(2026-10-04) | -|---|---|---| -| Q1 | 三生产者合成序(全局变异③号卡 × 玩家配比 × 偏运硬币挤同一摇号投影点) | **甲**:表值 → 玩家配比差值 → 玩家包络钳制 → 图级变异偏斜 → 同调硬币偏置——包络只管玩家自己的差值;变异与硬币是设计者侧投影、在玩家锁域后叠加、互不受钳;玩家可部分对冲变异(对冲深度 = 包络宽度 ÷ 变异幅度比值,归需求卡片调参),③号卡「把全通率压向带 A」的杠杆不被配比击穿。§6.3 原合成序草图(同调偏置在配比前)经本裁废止。与变异报告「图级先、件级后」的相对次序相容 | -| Q2 | 图鉴柜台商品形态 | **甲 逐型商品**——每型「可调权」一件商品、玩家自选解锁顺序(与三柜台商品语言同构;解锁顺序=构筑元博弈;存档域=逐型解锁位)。分级大件淘汰(柜台语言退化反噬 P3 裁决) | -| Q3 | 预设档换档时点 | **甲 冒险开始选档、中途锁死**(与「冒险前预设」原案措辞及同调件「中途不可换装」先例一致;start_adventure 一次注入零改造)。关间可换登记为后置评估(触发条件=真人试玩证实「读敌换阵」需求) | -| Q4 | 包络宽度是否随容量分级 | **乙 随容量分级放宽**(窄→宽;只放宽不收窄 ⇒ 存档差值向量零迁移;钳制按当前容量档包络执行) | - -**自答登记六件(事实/后置,无需裁决,随批入账)**:① 包络数值、下限具体值、挪点粒度(=整数权重)归需求卡片+调参批,且**须待迭代 14 ②号卡判式换代落定后按新当量账复算再转正**(配比包络的定值锚 = 新判式,不在旧账投值);② 护栏带挂带与极端角集合归实施批验收口径节;③ 图鉴柜台商品换算率登记 = 0 平移 + 护栏带链接,随上架批;④ 容量定价归元进度设计批既有归口;⑤ ①号卡初始展开收窄使可摇格变少、配比统计涨落变大——读数自然吸收,不另立机制;⑥ 行动经济对账按配比包络复算(06 影响面在册)——挪点制总量恒 77,复算只剩分布位移一个变量。 - -**编卡(2026-10-04 用户令)**:本提案(八裁全落)编入 roadmap **迭代 14 ⑥号卡**——序位在②③之后(Q1 合成序扩参与包络数值锚依赖②判式换代与③变异投影,三者共用摇号投影点);§九「实施窗口 = Alpha」随编卡改迭代 14;包络数值 provisional 起步,真人读数到账后校准;需求卡片待起草(估时随需求卡片,前置换用户发令)。 - ---- - -## 附:外部先例出处清单(检索日期 2026-10-04) - -| 先例 | 出处 | -|---|---| -| Loop Hero 卡组/营地解锁/复制上限 | 官方玩法说明;Kakuchopurei 指南(loop-hero-guide);RPG.net / Quarter to Three 论坛讨论(金卡唯一、卡解锁深度) | -| Hades 夜之镜(黑暗点数、槽位双选项、钥匙解锁变体、无全洗点) | Hades Fandom Wiki「Mirror of Night」条目;Steam 社区讨论(app/1145360) | -| Dead Cells 自定义模式(池筛选少于 20 件锁成就) | Reddit r/deadcells「Let's talk about the changes to the achievement unlock」;官方补丁说明(Whack-a-mole 更新 2021-03) | -| Risk of Rain 2 指令神(自选道具=支配策略) | 官方 Wiki「Artifacts」条目;社群共识 | -| 杀戮尖塔(开局四选一 / 进阶等级 / 遗物分池保底) | 官方 Wiki(Slay the Spire Wiki:Neow / Ascension / 遗物保底) | -| 桌面战棋编制(总点数 + 类别上下限) | 战锤 40K 兵力编制组织图规则(桌上通识,历代版本一致);D&D/Pathfinder 点数购点法 | -| 本仓在库调研(营地/跨局结构四家) | active.md 台账 66(元进度定稿批,2026-09-18,Hades/Dead Cells/Loop Hero/Rogue Legacy) | diff --git a/reports/需求卡片-冒险前资源配比预设批-2026-10-04.md b/reports/需求卡片-冒险前资源配比预设批-2026-10-04.md deleted file mode 100644 index 122ed0b7..00000000 --- a/reports/需求卡片-冒险前资源配比预设批-2026-10-04.md +++ /dev/null @@ -1,99 +0,0 @@ -# 需求卡片:冒险前资源配比预设批(迭代 14 ⑥号卡) - -> 日期:2026-10-04 -> 性质:实施批需求卡片(零拍板点——八裁全落,本批只做实现;provisional 数值随出口人闸追认) -> 裁决事实源:[提案-冒险前资源配比预设-2026-10-04](提案-冒险前资源配比预设-2026-10-04.md)(已退役存档,2026-10-18 用户令入库——只作推导与淘汰依据的历史档;裁决结论已录 [06-待办清单](../design/gdd/06-待办清单.md)「冒险前资源配比预设(构筑提案)」条)§八备选全列 + §九立项五裁 + §十盘问四裁与自答六件(**唯一裁决源,本需求卡片不复述推导、只引用结论**) -> 排位:roadmap 迭代 14 ⑥号卡(2026-10-04 用户令编入);06-待办清单「冒险前资源配比预设(构筑提案)」条(已裁待实施) -> **开工门 = ②号卡(判式换代)与③号卡(全局变异首发)出口已合并进 master**——理由见 §三 -> 估时:T0-T8 ≈ **6.0 日**(两段切分预案见 §十) -> **⚠ 已冻结挂起(2026-10-05 用户令)**:本批移出 roadmap 迭代 14(⑥号卡撤销)——暂不实施,需求卡片冻结保留;06-待办清单同条目转挂起。重启须用户另行发令;届时按届时 master 态重校 §三开工门(②判式换代与③全局变异若已入库则自动解)。 - ---- - -## 一、批任务一句话 - -玩家在**开始冒险前**预设格面九型资源的类型分布(预设分布、不预设落位——装板摇号仍定落位),配比经差值向量在摇号前注入权重合成层;容量(可调型/包络宽度/预设档)走营地奖励格图鉴柜台逐型解锁。机制件 + 构筑界面(程序占位)+ 柜台商品 + 存档 v6 + 验收闸一批交付。 - -## 二、裁决基座速览(全文见提案 §九§十) - -| # | 裁决 | 结论一行 | -|---|---|---| -| P1 | 预算机制 | 组合制:总量挪点(九型正向权重总和恒 77,ΣΔ=0)+ 每型上下限包络 + 容量解锁渐进;gp 首发不可调〔包络 (2,2)〕/预设档 3 套/下限 >0 | -| P2 | 主权与验收 | 设计者定包络与默认、玩家包络内分配;验收 = 默认分布锚(现行四带权威读数不动)+ 极端构筑护栏带 | -| P3 | 容量柜台 | 奖励格图鉴承接,**逐型商品**(每型可调权一件、玩家自选解锁顺序) | -| P4 | 注入语义 | 差值向量(存相对默认偏移 ΣΔ=0;集合操作只保留在解锁层——可调型集扩容用追加语义) | -| P5 | 渠道边界 | 清图槽差异化先行 + 三句不混池口径随批入文(板图分工/配比域封闭/格型无差) | -| Q1 | 合成序 | 表值 → 玩家配比差值 → 包络钳制 → 图级变异偏斜(③号卡产物)→ 同调硬币偏置;**包络只管玩家差值**,设计者侧两投影后叠互不钳 | -| Q3 | 换档时点 | 冒险开始选档、中途锁死(关间换档后置评估,本批不做) | -| Q4 | 包络分级 | 包络宽度随容量分级放宽(只放宽不收窄,存档差值零迁移) | - -## 三、序位与开工门 - -- **开工门 = ②③号卡出口**。理由两条:① Q1 合成序的插入位 = ③号卡扩参后的同一摇号投影点(`reward_economy._biased_cell_rows()` 一带,偏运硬币先例)——②③先行入库使本批只做一次合成层改造,不做二次返工;② 包络数值的定值锚 = ②号新判式当量账(提案 §十自答①)——不在旧账上投值。 -- 出口读数与②③**分开归因**(roadmap 在册口径);本批 MC off 态对拍锚以②③入库后的现行锚为基线。 - -## 四、红线(批内零触碰面) - -1. **结构冻结四件零 diff**:危险格与安全格同池(同池同分布——配比是整体平移,安全格与危险格仍同一分布)/ 九型封闭枚举 / 装板期一次摇定无重摇 / 负向恒即时无库存。配比只动正向池权重的合成,冻结面零触碰。 -2. **确定性契约**:`reward_roll` 专属随机流取号次数只随非封锁格数、不随权重;**off 态(全零差值 + 无解锁)逐位等于现行结果**——本批第一断言。 -3. **结算零改**:DamageController、兑付编排、极性矩阵、percent 行效果引用零 diff;配比不改任何单次兑付的语义,只改触发频率。 -4. **流程红线**:worktree 铁律(`../g-plan2-a4ratio-<日期>`);主区在飞脏文件不碰不代提交;两入口铁规(冒烟挂批末闸,短会话零测试)。 - -## 五、任务卡 - -### T0 开工三查 + 基线锚确认(0.25d) -查前序出口(②③号卡已入库(master log 与 06 状态吻合));查主区脏文件(在飞件标旗不碰);查 ②③ 入库后的现行 MC off 对拍锚(本批 off 对拍权威锚,只测不判口径沿用)。**出**:开工闸记录。 - -### T1 配比参数表 schema + registry + provisional 投值(0.5d) -新建 `ratio_preset_table`(配比参数表,`data/tables/`):每容量档一行(可调型集合 / 每型包络 [下限,上限] / 预设档位数);九型默认分布引用(=现行 `reward.tres` 正向行权重,**不复制值、引用表**);gp 白名单行(包络 (2,2) 恒定,不上柜台)。数值全部 provisional:包络按②号新判式当量账复算起步(pierce/magic 代价轴最贵 → 包络上界收紧方向),下限统一 2 起步,容量三档排布(一档 2 型可调+窄包络 → 二档 5 型+中包络 → 三档 8 型+全包络,具体归 T7 读数后调)。registry 新条目 + save_file 域登记(配比三档 + 逐型解锁位 + 容量档 + 上次使用档)。**验收**:加载校验(词表成员/包络下限>0/ΣΔ=0 格式校验)通过;白板存档(无解锁无配比)逐位等于现行。 - -### T2 摇号前合成层扩参(1d) -`reward_economy` 合成层改多生产者单点:**表值 → 玩家配比差值 → 包络钳制(按当前容量档)→ 图级变异偏斜(③号卡投影,原样保留)→ 同调硬币偏置(原样保留)→ 加权抽取**。包络只钳玩家差值(Q1 甲);luck_bias=0 且 Δ=0 时逐位返回原行集(现行为)。**验收**:单元断言——off 零扰动逐位 / 钳制上下边界 / ΣΔ≠0 输入拒绝 / 包络放宽后老差值合法(Q4)/ 变异与硬币不受玩家包络钳制(Q1)/ 同种子同预设同身份表。 - -### T3 存档 v6(0.5d) -配比域入档:三预设档(各 = 九型 Δw 整数向量 + 命名)+ 逐型解锁位图 + 容量档 + 上次使用档引用。迁移幂等(v5 → v6 无配比域 = 全零向量 + 零解锁,白板成立)。**验收**:v5 存档升级后冒烟全绿 + off 态逐位;断点存取往返逐位。 - -### T4 图鉴柜台逐型商品(0.5d) -奖励格图鉴柜台改写「格面构筑」:八件可调权商品(八型各一件;gp 不上架)+ 容量档推进商品三件(或与可调权合并触发——按参数表排布行执行,二择一在 T1 定)。购买 = 钥石直购、几何递增定价(provisional)。柜台语义改写连带:原「特殊奖励格」悬置内容保持悬置(独立问题不销号,呈现「筹备中」态)。**验收**:未解锁型在构筑面不可编辑(灰态);解锁后差值可存;定价读表。 - -### T5 构筑界面程序占位(1.5d) -主菜单入口「配比预设」管理面:三档页签,每档 = 八可调型 +/− 步进(显示当前权重/默认权重/包络区间/剩余可挪池)、ΣΔ=0 实时校验(非法态禁存)、gp 行只读灰态;开始冒险时选档确认(默认 = 上次使用档;不选 = 默认分布)。冒险开始后锁档(Q3)。程序占位质量,视觉终稿归 HUD 协作批(与营地三柜台同路径,不设样张闸)。**验收**:ui_input_probe 新增构筑面用例(编辑/存档/选档/锁档四态);零差值编辑路径不触碰任何摇号(off 纪律)。 - -### T6 注入通道(0.25d) -`start_adventure` params 扩 `ratio_delta`(九型 Δ 向量)+ 档引用;LevelRunner 透传至战斗初始化(偏运硬币 luck_bias 同路先例)。**验收**:参数面字段与元进度「属性=数值覆盖、池=集合操作」分工注记一致(配比差值 = 数值域新生产者);off 态逐位。 - -### T7 验收闸(1d) -冒烟断言批:off 逐位 / 钳制边界 / 锁档 / 解锁门控 / 柜台满购 / ΣΔ 校验拒非法。MC:off 态与 T0 锚逐位对拍;on 态极端构筑代表组(最大化直伤 / 最大化续航 / 最大化经济三组 + 默认对照,N 按现行 MC 纪律二档)——**极端角与默认分布的带差 = 护栏带首批读数**(带值判定归出口人闸,本批只测不判)。行动经济对账按包络复算(总量恒 77,只余分布位移变量)。**停机点 S1**:极端角把现行带打穿且包络收窄不能救 → 停机呈报(见 §七)。 - -### T8 文档六现行事实源 + registry + 06 + 出口包(0.5d) -[地图战斗资源](../design/gdd/systems/战斗循环/地图战斗资源.md)(候选集合节:权重来源 = 表值 × 玩家覆盖合成,Q1 序落文;三句不混池口径入纪律表);[元进度](../design/gdd/systems/跨局循环/元进度.md)(图鉴柜台职责改写 + 容量商品 + 存档第五件);[冒险结构](../design/gdd/systems/冒险循环/冒险结构.md)(参数面字段);[难度曲线与Boss](../design/gdd/systems/冒险循环/难度曲线与Boss.md)(护栏带 + 包络 provisional 行);[HUD 框架](../design/gdd/systems/表现与支撑/HUD框架.md)(构筑界面槽位 + 视觉终稿后置登记);[行动经济](../design/gdd/systems/排雷循环/行动经济.md)(对账窗口注记)。registry 全条目。06 状态推进(已裁待实施 → 随批落地状态)。出口包 = 人工检查清单 + provisional 追认清单 + off/on 读数对照。 - -## 六、provisional 清单(出口人闸逐项追认) - -包络数值表(含②新判式复算依据)/ 下限值(2 起步)/ 容量三档排布 / 柜台定价起步价与公比 / MC 代表角集合与护栏带首批读数。 - -## 七、停机点 - -- **S1**:T7 极端角把现行带打穿(带 A/B 越界)且包络收窄两档内救不回 → 停机呈报包络重定案(拍板归用户)。 -- **S2**:③号卡变异投影的实现形态与 Q1 序在代码层冲突(投影点不可插位)→ 停机对齐,不得自行改③已落语义。 -- **S3**:批内任一时点冒烟基线漂移 >2% 且归因不在本批改动面 → 停机呈报(并行会话竞态排查)。 - -## 八、批末闸与出口 - -冒烟全量 + run_all 15 套 + 探针五绿(ui_input/ui/ui_layout/fx/t17_defer)+ doc 三闸基线同款零新增 + 熵增 PASS + MC off 逐位 + **主区合并后冒烟复跑**;出口包交付后待人闸(与迭代 14 全迭代出口合并过闸,序位口径在册)。 - -## 九、红线复核清单(合并前自查) - -- [ ] 危险格与安全格同池/九型封闭/一次摇定/负向恒即时四冻结零 diff -- [ ] reward_roll 取号次数与权重无关(断言在) -- [ ] DamageController/极性矩阵零 diff -- [ ] off 态逐位 = 现行(T0 锚对拍 PASS) -- [ ] ②③号卡已落语义零改写(变异投影/判式工具常量只读) - -## 十、估时汇总与切分预案 - -| 卡 | T0 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | 合计 | -|---|---|---|---|---|---|---|---|---|---|---| -| 估时 | 0.25 | 0.5 | 1 | 0.5 | 0.5 | 1.5 | 0.25 | 1 | 0.5 | **≈6.0d** | - -**切分预案**(迭代 14 尾部余量不足时启用,代排待追认):段一机制批 = T0-T4 + T6 ≈ 3d(合成层/存档/柜台/通道全落,构筑面暂用最小命令行式编辑);段二界面闸批 = T5 + T7 + T8 ≈ 3d。两段各自独立 worktree 合并、各自过批末闸。 diff --git a/roadmap.md b/roadmap.md index 241a6335..a0626c47 100644 --- a/roadmap.md +++ b/roadmap.md @@ -6,7 +6,7 @@ - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 - **迭代位**:迭代 1–50 全部已完成——迭代 50(敌方状态个体化+断言补齐+架构审查 3.1+3.2 整改三卡)2026-11-03 合回(合并提交 8d3a12ec,独立验收审计两轮终判通过〔首轮缺漏三条→整改笔 6cb1cf5e→复验通过〕),批后收尾闭合(2026-11-03):反哺点十条处置闭合(增益状态 v1.6 删「实装随该批」过时行/architecture v1.32 排雷规则件 preview_reveal 只读预览口登记+天赋树 tree_rules 参数字典 API 换代/奇物 v1.5 cap 必填与取值域写入/覆盖域表减动效行翻已覆盖;SPAWN_AREA_CELLS、shop.spend_discount 表字段、battle_driver 签名三处零登记零落点,天赋树 v0.12 等五份与术语表零反哺确认)、实机目检五项登记体验验证清单 ㊿〔实现报告原编㊾与 CI 迁远端批撞号,改用㊿〕、MC 锚代表性注记落 sim-data/README(重跑归一档用户发起)、产出物退役六件(三需求卡片+反哺点+实现报告+验收审计——吸收即死+证据只留最新)、待办清单三卡条目整条删除(架构审查余项〔审计待裁点④⑤⑥⑦〕按未决先迁再死另立新条登记)。迭代 49 收尾记录见 git 提交 aa396eb9,迭代 48 见 bd54d1f4。 -- **下一步**:迭代 51 已编入待开工(四卡 ≈6.625 工作日,`/iter 51` 发令)。待编池:改写族孤儿(待处置方向裁决)、冒险前资源配比预设(冻结备选)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 +- **下一步**:迭代 51 已编入待开工(四卡 ≈6.625 工作日,`/iter 51` 发令)。待编池:改写族孤儿(待处置方向裁决)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 ## 里程碑时间线 diff --git a/待办清单.md b/待办清单.md index 4956edfb..3b0fbd45 100644 --- a/待办清单.md +++ b/待办清单.md @@ -127,12 +127,12 @@ #### 冒险前资源配比预设(构筑提案)(2026-09-18 登记,来源=会话提及) -**实况**:构筑提案 2026-09-18 立项、八项裁决全裁(2026-10-04),设计闭合机制=[元进度](design/gdd/systems/跨局循环/元进度.md)「冒险开始参数面与覆盖值通道」(零新机制);2026-10-05 挂起——移出 roadmap 迭代 14,八项裁决与需求卡片冻结保留,重启须用户另行发令;全案与淘汰依据原载已退役提案(git 历史可考),裁决细节冻结在卡片。关联文档:[元进度](design/gdd/systems/跨局循环/元进度.md) v1.0(闭合凭据;现行 v1.25 已复核在役,2026-10-26 文档体检第 10 项);需求卡片 reports/需求卡片-冒险前资源配比预设批-2026-10-04.md(文件头挂冻结横幅)。 +**实况**:设计闭合机制=[元进度](design/gdd/systems/跨局循环/元进度.md)「冒险开始参数面与覆盖值通道」(零新机制);八项裁决 2026-10-04 全裁,2026-10-05 起挂起(移出 roadmap 迭代 14),重启须用户另行发令。关联文档:[元进度](design/gdd/systems/跨局循环/元进度.md) v1.0(闭合凭据;现行 v1.25 已复核在役);立项提案与需求卡片已退役(2026-11-04 用户令删除,八项裁决与全案依据随件入 git 历史可考)。 - 相关文档:[地图战斗资源](design/gdd/systems/战斗循环/地图战斗资源.md)修订批(基础配比的权重覆盖语义 + GDD 修订)与[元进度](design/gdd/systems/跨局循环/元进度.md)(解锁容量与注入生产者)共裁;呈现面归[HUD 框架](design/gdd/systems/表现与支撑/HUD框架.md)与[教学引导](design/gdd/systems/表现与支撑/教学引导.md)顺带。 **改动面**:文档 -**状态**:文档设计已闭合+已写卡(备选) +**状态**:文档设计已闭合+待写卡(备选)〔用户特批 2026-11-04 退役卡片与提案回退待写卡〕 #### 袋装摇号(格面资源池结构备选——保底型增多后的重构方向)(2026-10-07 登记,来源=会话提及) From 490ea35c532819f71c891fbe4a8695f4dbd9d8b1 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 07:51:06 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E8=BF=AD=E4=BB=A351?= =?UTF-8?q?=E8=BF=9E=E5=B8=A6=E9=9D=A2=E9=9A=8F=E8=BF=81):=20=E5=9B=9B?= =?UTF-8?q?=E7=BA=A2=E9=A1=B9=E6=B5=8B=E8=AF=95=E4=B8=8E=E6=A0=87=E6=B3=A8?= =?UTF-8?q?=E8=A1=A5=E6=94=B9=EF=BC=8C=E8=A1=8C=E4=B8=BA=E9=9B=B6=E5=8F=98?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 归因: 四条红全部由迭代 51 合回(fb27dc2f)引入,重塑后两笔—— 1cd6e2b1 卡①(effect_engine.gd:14 删 charge_full 词表位)与 372b4fdc 卡②(damage_controller.gd:50 _init 加 facts_ref 第五参;slot_store.gd:155 migrate_legacy_save 公开化;breakpoint_store 新增 13 公开读写方法)。 首现于 run 56(5d7579e7 合回,因动 game/tests/.gates/.gitignore 被判 full 域)—— 迭代 51 之后第一次代码域全量真跑;run 51 倒在导入步(runner 故障)、run 54 实为 域=docs、run 55 零触发跑闸步 skipped,故这三跑都没照出。 - 随迁面: 1) forge_table_test.gd:219 EVENTS 闭集计数 22→21(卡①残留面清单 13 处+注释 3 处 未含测试内闭集计数断言,属卡片考证缺面)。 2) system_contracts_test.gd 补 BattleFactsScript preload 与 :108 第五参;:102 注释 「四个依赖」改「五个依赖」(定型标注面同步)。 3) save_manager_domain_test.gd:481/492 改公开名 migrate_legacy_save。 4) breakpoint_store.gd:225 bind_save_manager 参数补类型 sm: SaveManagerCore(09 R3)。 - 验证: 三套件单跑 exit 0(forge_table/system_contracts/save_manager_domain); audit_type_hints PASS 产品码类型闸归零;--code 档 09 静态扫描已离红名单。 - 一条与本轮无关的本机红在案: settlement_wiring_snapshot_test V1 同种子 golden 对拍 本机(Windows)红、远端(Mac,同字节 run 56)绿——把码面改动全摘回纯净 master 单跑 仍红,故非本批引入;golden 平台绑定成因未查,另条登记待裁。 --- game/core/breakpoint_store.gd | 2 +- game/tests/unit/contracts/system_contracts_test.gd | 7 ++++--- game/tests/unit/core/save_manager_domain_test.gd | 4 ++-- game/tests/unit/data_config/forge_table_test.gd | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/game/core/breakpoint_store.gd b/game/core/breakpoint_store.gd index 699a5367..85af6d25 100644 --- a/game/core/breakpoint_store.gd +++ b/game/core/breakpoint_store.gd @@ -222,7 +222,7 @@ func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary # ---- 断点域六成员公开读写面(5.1 封装修复——宿主私取清零:方法体一律一行转发自有成员,零逻辑增量)---- # 断点承载注入(宿主构造后接线——save_manager 后绑) -func bind_save_manager(sm) -> void: +func bind_save_manager(sm: SaveManagerCore) -> void: _save_manager = sm diff --git a/game/tests/unit/contracts/system_contracts_test.gd b/game/tests/unit/contracts/system_contracts_test.gd index 4c212e80..350e1308 100644 --- a/game/tests/unit/contracts/system_contracts_test.gd +++ b/game/tests/unit/contracts/system_contracts_test.gd @@ -13,6 +13,7 @@ const RngServiceScript := preload("res://core/rng_service.gd") const StatusContainerScript := preload("res://core/status_container.gd") const UnitContainerScript := preload("res://core/unit_container.gd") const DamageControllerScript := preload("res://core/damage_controller.gd") +const BattleFactsScript := preload("res://core/battle_facts.gd") const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇总退出样板单点 @@ -98,13 +99,13 @@ func _test_ticket_nine_columns() -> void: func _bare_controller(): var db = ConfigDBScript.new() - # 只取 open_ticket 纯构造面——四个依赖一律传真实实例,不用空桩: - # DamageController 的 db/rng/units/effects 全是定型标注,传无类型的空桩会触发 + # 只取 open_ticket 纯构造面——五个依赖一律传真实实例,不用空桩: + # DamageController 的 db/rng/units/effects/facts 全是定型标注,传无类型的空桩会触发 # 「Trying to assign value of type '' to a variable of type '…'」脚本错误——该错误不改退出码, # 却让赋值静默失败(成员留 null),脚本错误扫描落地后必红。三者均可零消费构造。 var units = UnitContainerScript.new(db) var effects = StatusContainerScript.new(db) - return DamageControllerScript.new(db, RngServiceScript.new(), units, effects) + return DamageControllerScript.new(db, RngServiceScript.new(), units, effects, BattleFactsScript.new()) # ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ---- diff --git a/game/tests/unit/core/save_manager_domain_test.gd b/game/tests/unit/core/save_manager_domain_test.gd index 26211e86..4574f1c9 100644 --- a/game/tests/unit/core/save_manager_domain_test.gd +++ b/game/tests/unit/core/save_manager_domain_test.gd @@ -478,7 +478,7 @@ func _smoke_save_slots() -> void: fsrc.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 1234}})) fsrc.close() DirAccess.remove_absolute(slot_tpl % 1) - ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak") + ml.slots.migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak") var migrated: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1)) Kit.check("sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕", int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234) @@ -489,7 +489,7 @@ func _smoke_save_slots() -> void: var fsrc2 := FileAccess.open(legacy_src, FileAccess.WRITE) fsrc2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 777}})) fsrc2.close() - ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak") + ml.slots.migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak") var slot1_now: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1)) Kit.check("sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕", int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 diff --git a/game/tests/unit/data_config/forge_table_test.gd b/game/tests/unit/data_config/forge_table_test.gd index 226a5114..7bca3562 100644 --- a/game/tests/unit/data_config/forge_table_test.gd +++ b/game/tests/unit/data_config/forge_table_test.gd @@ -216,5 +216,5 @@ func _test_negative_cases() -> void: func _test_effect_kinds_closed() -> void: Kit.check("效果引擎效果类型闭集 32 种(甲案词表退役——overflow/attack_segments 摘名)〔锻造工坊批 V11〕", EffectEngineScript.EFFECT_KINDS.size() == 32) - Kit.check("效果引擎事件词表在册 22 种(迭代 46 换图系扩签 refresh_request/board_refreshed 换代)〔锻造工坊批 V11〕", - EffectEngineScript.EVENTS.size() == 22) + Kit.check("效果引擎事件词表在册 21 种(charge_full 位摘名——蓄力层删键批)〔锻造工坊批 V11〕", + EffectEngineScript.EVENTS.size() == 21) From 0ffa65ea01801031f876a706518318abcac5e19a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 08:29:30 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E6=8E=A2=E9=92=88=E5=8D=95=E4=B8=80=E5=BD=A2=E6=80=81=C2=B7?= =?UTF-8?q?=E6=97=97=E6=A0=87=E9=80=80=E5=BD=B9):=20probe=5Fgate=20--click?= =?UTF-8?q?/--full=20=E4=B8=A4=E6=97=97=E6=A0=87=E9=80=80=E5=BD=B9?= =?UTF-8?q?=E6=94=B9=E5=8D=95=E4=B8=80=E5=BD=A2=E6=80=81=E5=8D=81=E4=BB=B6?= =?UTF-8?q?=E6=81=92=E8=B7=91(=E9=BB=98=E8=AE=A4=E4=BA=94=E6=AE=B5+?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E8=B5=B0=E6=9F=A5+=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E5=9B=9B=E4=BB=B6,=E5=90=84=E6=AE=B5=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E8=A1=8C=E4=B8=8E=E5=8E=9F--click=20--full=E5=90=8C=E4=BC=A0?= =?UTF-8?q?=E9=80=90=E5=AD=97=E8=8A=82=E5=90=8C=E5=BD=A2=3D=E9=9B=B6?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E5=8F=98=E5=8C=96,=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=9D=A2=E5=8F=AA=E5=89=A9[game=E7=9B=AE=E5=BD=95]);=20run=5Fa?= =?UTF-8?q?ll=5Fgates=2003=E8=B0=83=E7=94=A8=E5=8E=BB=E6=97=97=E6=A0=87+?= =?UTF-8?q?=E5=A4=B4=E6=B3=A8=E4=B8=A4=E5=A4=84iter27=E6=8F=90=E6=B3=95?= =?UTF-8?q?=E6=94=B9=E3=80=8C=E6=BC=8F=E8=B7=91=E5=9C=BA=E6=99=AF=E6=8E=A2?= =?UTF-8?q?=E9=92=88=E6=89=A9=E5=B1=95=E6=AE=B5=E3=80=8D;=20render=5Fgate?= =?UTF-8?q?=E5=A4=B4=E6=B3=A8fx=5Fprobe=E5=BD=92=E5=B1=9E=E6=94=B9?= =?UTF-8?q?=E6=8C=8703=E5=9C=BA=E6=99=AF=E6=8E=A2=E9=92=88=E9=97=B8;=20tes?= =?UTF-8?q?ts/README=E5=B7=A5=E5=85=B7=E8=A1=8C=E6=94=B9=E5=8D=95=E4=B8=80?= =?UTF-8?q?=E5=BD=A2=E6=80=81;=20architecture=20probe=5Fgate=E8=A1=8C?= =?UTF-8?q?=E5=8D=81=E4=BB=B6=E6=81=92=E8=B7=91+=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=9F=9F=E9=97=B8=E5=88=97=E5=8E=BB=E5=8F=8C=E6=97=97=E6=A0=87?= =?UTF-8?q?;=20process-conventions:52=E4=B8=8E=E4=BD=93=E9=AA=8C=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=B8=85=E5=8D=95=E6=A0=B8=E9=94=80=E6=B3=A8=E8=AE=B0?= =?UTF-8?q?=E4=B8=A4=E5=A4=84=E5=8E=86=E5=8F=B2=E5=8F=99=E8=BF=B0=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E5=9C=A8=E6=A1=88(=E8=AE=B0=E5=BD=95=E5=BD=93?= =?UTF-8?q?=E6=97=B6=E4=BA=8B=E5=AE=9E);=20=E8=B5=B7=E5=9B=A0=3D2026-11-04?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=A3=81=E5=8B=98=E8=AF=AF:55a8f270=E4=B8=A4?= =?UTF-8?q?=E9=97=B8=E5=B9=B6=E4=B8=80=E9=97=B8=E6=97=B6=E6=97=97=E6=A0=87?= =?UTF-8?q?=E5=BA=94=E4=B8=80=E5=B9=B6=E6=94=B6=E6=8E=89,=E8=AF=AF?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E4=B8=BA=E5=BC=80=E5=8F=91=E6=9C=9F=E5=8D=95?= =?UTF-8?q?=E8=B7=91=E5=BC=80=E5=85=B3;=20=E9=AA=8C=E8=AF=81:=20bash=20-n?= =?UTF-8?q?=E4=B8=89=E4=BB=B6+probe=5Fgate=E6=97=A0=E6=97=97=E6=A0=87?= =?UTF-8?q?=E6=95=B4=E8=B7=91=E5=8D=81=E6=AE=B5(9=E6=AE=B5=E7=BB=BF+ui=5Fp?= =?UTF-8?q?robe=E9=A6=96=E8=B7=91=E7=BA=A2=3D=E5=8C=BA=E7=BC=BA=E6=9C=AA?= =?UTF-8?q?=E5=85=A5=E5=BA=93.import=E8=BE=B9=E8=BD=A6=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E6=88=90=E5=9B=A0,=E8=A1=A512=E4=BB=B6=E8=BE=B9=E8=BD=A6?= =?UTF-8?q?=E5=90=8E=E6=9C=80=E5=B0=8F=E5=8D=95=E5=85=83=E5=A4=8D=E8=B7=91?= =?UTF-8?q?exit0=E7=BB=BF)+--docs=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E9=9B=B6=E6=96=B0=E5=A2=9E=E7=BA=A2+grep=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=E9=9B=B6=E6=AE=8B=E7=95=99(=E8=B1=81=E5=85=8D=3D=E4=B8=A4?= =?UTF-8?q?=E5=A4=84=E5=8E=86=E5=8F=B2=E5=8F=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engineering/architecture/architecture.md | 4 +- game/tests/README.md | 2 +- tools/probe_gate.sh | 76 +++++++++---------- tools/render_gate.sh | 2 +- tools/run_all_gates.sh | 6 +- 5 files changed, 42 insertions(+), 48 deletions(-) diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index cbc0a6ce..e6660be0 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -249,7 +249,7 @@ | **run_smoke**(冒烟测试运行器) | `tools/run_smoke.py` | 冒烟测试总入口与判定权:headless 以项目运行目标拉起 `res://tests/smoke_boot.tscn`(极小启动场景——autoload 在位、配置表全载、主屏实例化,零逻辑判定)+ 三闸判定 | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备、判定无洞:①退出码 = 0;②摘要行「[smoke] 摘要:N 项检查,0 项失败」在案且失败数为 0;③输出扫 `SCRIPT ERROR` / `Parse Error` 标记行。外部 wall-clock 超时在位(超时 = FAIL) | | **run_system_test**(系统测试运行器) | `tools/run_system_test.py` | 系统测试总入口:headless 跑真实玩法路径断言面(宿主=`res://tests/system_test.gd`)+ 三闸判定(口径同 run_smoke,摘要行兼容两种宿主格式) | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备(同 run_smoke:退出码/摘要失败数/脚本错误扫描;wall-clock 超时在位) | | **run_all**(单元与集成测试) | `game/tests/run_all.gd` | 64 套统一入口(61 单元+3 集成,2026-10-28 实数):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸);选跑态 `--only=<词[,词…]>`(直跑形态对套件路径子串匹配只跑命中套件,零命中=用法错 quit(2),仅供开发期最窄确认——不产闸回执、不作验收凭据,批末全量闸不经选跑分支) | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;三硬闸齐备(入口内扫子套件输出 + 编排层扫 `unit.log`;`Kit.finish` 零断言返回专用码 2)。子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空 | -| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排:默认段 ui_input_probe / ui_struct_probe / layout_probe / ui_contrast_probe / **screen_flow_probe**(屏壳切换),`--click` 加跑 ui_click_probe,`--full` 加跑 ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);判定统一走 `verdict` 函数——退出码为唯一权威判据,PASS 串降级为附加判据,逐段跑 `tools/error_scan.py` | +| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排(单一形态十件恒跑):ui_input_probe / ui_struct_probe / layout_probe / ui_contrast_probe / **screen_flow_probe**(屏壳切换)/ ui_click_probe(行为走查)/ ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);判定统一走 `verdict` 函数——退出码为唯一权威判据,PASS 串降级为附加判据,逐段跑 `tools/error_scan.py` | | **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零;⑤三段探针日志逐段脚本错误扫描 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);SCRIPT ERROR 扫描在位(逐段 `tools/error_scan.py`,标记与 run_smoke 同源);断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) | | **static_scan**(静态扫描域) | `game/tests/static_scan.sh` | 静态扫描域 S1–S10 可执行载体(grep 规则 + `tools/audit_type_hints.py`〔S9〕+ `tools/audit_file_headers.py`〔S10〕) | fail 聚合退出码(任一硬失败规则命中即 exit 1) | 查退出码在位;SCRIPT ERROR 扫描与断言数下限不适用(逐规则命中即红,无运行时错误面与断言计数);S3 现为 warning 级、不参与 fail 判定(见下表) | @@ -257,7 +257,7 @@ | 域 | 闸 | 本批改动落在这些路径即属该域 | |---|---|---| -| **代码域** | run_smoke、run_system_test、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 | +| **代码域** | run_smoke、run_system_test、run_all、probe_gate、render_gate、static_scan | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 | | **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` | | **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — | diff --git a/game/tests/README.md b/game/tests/README.md index 50156660..557261e5 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -233,7 +233,7 @@ tests/ | `tools/run_smoke.py`(冒烟测试) | 这版能否启动、能否加载表、能否进主屏——零逻辑判定 | 退出码+脚本错误扫描 | 开机路径/表加载改动后的快速自检;收口由闸编排 | | `tools/run_system_test.py`(系统测试) | 真实玩法路径的行为断言面(推理→战斗→结算→进度推进) | 退出码+脚本错误扫描 | 玩法链路改动的调试期确认 | | `tests/run_all.gd`(单元与集成) | 公式/状态机/契约/跨系统闭环(65 套件 manifest) | 退出码+三硬闸(见下);每套件子进程带截止时间守护——超时先 taskkill /T 整树回收再判该套件 FAIL、批继续(默认 120s,环境变量 `RUN_ALL_SUITE_TIMEOUT_SEC` 可覆盖);连续 3 个套件超时=红树整批挂死,第 3 个即中止整轮、汇总已见失败与超时清单后非 0 退出(阈值=可调常量,不连续的超时照旧逐个回收) | 新套件单跑调试;逻辑改动的最小单元复跑;开发期选跑——追加 `-- --only=<词[,词…]>` 只跑路径命中词的套件(零命中=用法错 exit 2;此形态仅供开发期最窄确认,不产闸回执、不作验收凭据);闸机关联档选跑——同一过滤机制加 `-- --only-source=gates`,由 `tools/run_all_gates.sh` 在 02 闸传入(回执 scope=related,是验收跑批本体) | -| `tools/probe_gate.sh`(场景探针,`--click`/`--full` 两模式) | UI 可达性、可见性、状态——真实实例化场景+真实输入路径 | 探针自检+退出码 | 新增 UI 区域的配套探针直跑与断言复核 | +| `tools/probe_gate.sh`(场景探针,单一形态十件恒跑) | UI 可达性、可见性、状态——真实实例化场景+真实输入路径 | 探针自检+退出码 | 新增 UI 区域的配套探针直跑与断言复核 | | `tools/render_gate.sh`(渲染 golden) | 画面基线——截图与基线图逐像素容差比对 | golden 比对脚本(依赖 PIL)+退出码/取证双检/三段探针日志脚本错误扫描(`error_scan.py`) | 主题/素材/排版类改动;窗口化运行,需开显示器 | | `game/tests/static_scan.sh`(S 域) | 项目纪律+第三方质量规则:RNG 单点/core 纯度/类型标注/文件头/注释禁词/旧符号残留;S13=gdstyle 质量规则(重复 load、循环内 await、循环内分配、不可达代码等真缺陷类;基线键=文件|规则|函数名(类/文件级=文件|规则,无数字条目带消息),数字漂移不假红,同键指标恶化超基线登记值即红) | 退出码(逐规则 FAIL 行) | 改产品码前后的快速自检;新增纪律先补扫描规则再修码 | | 测量件(`game/sim/`,逐件见下「游离脚本台账」) | 期望/分布读数——只给读数,不判对错 | 人工读 stdout 或配套工具脚本 | 调参与归因;MC 类一律用户发起(档位表见 `sim-data/README.md`「MC 采集档位表」) | diff --git a/tools/probe_gate.sh b/tools/probe_gate.sh index dcb7f2fb..d47b7af8 100755 --- a/tools/probe_gate.sh +++ b/tools/probe_gate.sh @@ -3,31 +3,29 @@ # 三闸:①每段 timeout 守卫+超时整树回收(挂死=FAIL 不静默;taskkill /T 连引擎子进程一起杀, # 不再只杀 wrapper 留孤儿)②段前预清+段后清本 game 目录孤儿 Godot # (按命令行精确匹配本 game 路径,不误杀其他 worktree 会话的合法进程)③末尾残留清点=批末闸(必须 0)。 -# 段:ui_input_probe=默认必跑;ui_struct_probe=默认必跑( -# 全 UI 屏结构断言:按钮互压/等宽/出屏/零尺寸/标题屏双列);ui_contrast_probe=默认必跑( -# UI 文字对比度机检 ≥4.5:1 无区域例外,测点表随改动维护);--click=加跑 ui_click_probe 行为走查 -# (自退出+退出码闸;超时多半=探针未适配新停点层,需做探针适配)。 -# layout_probe=默认必跑(分辨率几何断言,档位表是数据/断言是一份函数, +# 段(单一形态,十件恒跑): +# 默认段五件:ui_input_probe/ui_struct_probe( +# 全 UI 屏结构断言:按钮互压/等宽/出屏/零尺寸/标题屏双列)/ui_contrast_probe( +# UI 文字对比度机检 ≥4.5:1 无区域例外,测点表随改动维护)/screen_flow_probe(L3 屏壳切换)。 +# 行为走查段:ui_click_probe(自退出+退出码闸;超时多半=探针未适配新停点层,需做探针适配)。 +# layout_probe=默认段五件之一(分辨率几何断言,档位表是数据/断言是一份函数, # 增删档位只改表内一行;用 content_scale_size 改逻辑视口,headless 下真生效—— # get_window().size 在 headless 下不改变视口,其「1920 档恒定」类断言是 # 永远为真的假断言,禁用)。 # 判定口径(三硬闸):每段走 verdict 函数——①退出码为唯一权威判据(PASS 串降级为日志) # ②脚本错误扫描命中即红(tools/error_scan.py,标记与 run_smoke.py 同源)③PASS 串保留为附加判据。 -# --full 段(扩展段):ui_probe/ui_tune_probe/t17_defer_probe/fx_probe -# 四件带断言探针;默认段断言面零删减零改动(--full 只增不改)。 +# 扩展段(带断言探针四件):ui_probe/ui_tune_probe/t17_defer_probe/fx_probe。 # 渲染面归 render_gate.sh 正主(窗口化:shot_probe 七屏+fx_shot/board_shot 取证+棋盘 golden 逐像素)。 -# 用法: tools/probe_gate.sh [game目录] [--click] [--full] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) +# 用法: tools/probe_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) # 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell(Windows 侧);macOS/Linux 走 tools/gate_platform.sh # 共享跨平台件(Godot 路径两平台都可用 GODOT_BIN 环境变量覆盖)。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" . "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) -GAME=""; WITH_CLICK=0; WITH_FULL=0 +GAME="" for a in "$@"; do case "$a" in - --click) WITH_CLICK=1 ;; - --full) WITH_FULL=1 ;; *) GAME="$a" ;; esac done @@ -160,41 +158,37 @@ st=$? grep -E "screen_flow_probe" "$TMP/sfp.log" | tail -3 verdict "screen_flow_probe" "$TMP/sfp.log" "$st" "screen_flow_probe" || _rc=1 -if [ "$WITH_CLICK" -eq 1 ]; then - echo "======== ui_click_probe 行为走查 ========" - run_suite 240 "$TMP/clk.log" res://probe/ui_click_probe.tscn - st=$? - grep -E "click_probe" "$TMP/clk.log" | tail -4 - verdict "ui_click_probe" "$TMP/clk.log" "$st" "click_probe" || _rc=1 -fi +echo "======== ui_click_probe 行为走查 ========" +run_suite 240 "$TMP/clk.log" res://probe/ui_click_probe.tscn +st=$? +grep -E "click_probe" "$TMP/clk.log" | tail -4 +verdict "ui_click_probe" "$TMP/clk.log" "$st" "click_probe" || _rc=1 -# ── --full 段(四件带断言探针入闸;实测时长 ui_probe 17.4s/ +# ── 扩展段(四件带断言探针;实测时长 ui_probe 17.4s/ # ui_tune_probe 49.6s/t17_defer_probe 63.0s/fx_probe 21.9s,timeout 120=2-7×余量) ── -if [ "$WITH_FULL" -eq 1 ]; then - echo "======== ui_probe(--full 收编) ========" - run_suite 120 "$TMP/flp.log" res://probe/ui_probe.tscn - st=$? - grep -E "\[ui_probe\]" "$TMP/flp.log" | tail -2 - verdict "ui_probe" "$TMP/flp.log" "$st" || _rc=1 +echo "======== ui_probe(扩展段) ========" +run_suite 120 "$TMP/flp.log" res://probe/ui_probe.tscn +st=$? +grep -E "\[ui_probe\]" "$TMP/flp.log" | tail -2 +verdict "ui_probe" "$TMP/flp.log" "$st" || _rc=1 - echo "======== ui_tune_probe(--full 收编) ========" - run_suite 120 "$TMP/flt.log" res://probe/ui_tune_probe.tscn - st=$? - grep -E "\[tune_probe\]" "$TMP/flt.log" | tail -2 - verdict "ui_tune_probe" "$TMP/flt.log" "$st" || _rc=1 +echo "======== ui_tune_probe(扩展段) ========" +run_suite 120 "$TMP/flt.log" res://probe/ui_tune_probe.tscn +st=$? +grep -E "\[tune_probe\]" "$TMP/flt.log" | tail -2 +verdict "ui_tune_probe" "$TMP/flt.log" "$st" || _rc=1 - echo "======== t17_defer_probe(--full 收编) ========" - run_suite 120 "$TMP/fld.log" res://probe/t17_defer_probe.tscn - st=$? - grep -E "\[t17_defer\]" "$TMP/fld.log" | tail -2 - verdict "t17_defer_probe" "$TMP/fld.log" "$st" || _rc=1 +echo "======== t17_defer_probe(扩展段) ========" +run_suite 120 "$TMP/fld.log" res://probe/t17_defer_probe.tscn +st=$? +grep -E "\[t17_defer\]" "$TMP/fld.log" | tail -2 +verdict "t17_defer_probe" "$TMP/fld.log" "$st" || _rc=1 - echo "======== fx_probe(--full 收编) ========" - run_suite 120 "$TMP/flx.log" res://probe/fx_probe.tscn - st=$? - grep -E "\[fx_probe\]" "$TMP/flx.log" | tail -2 - verdict "fx_probe" "$TMP/flx.log" "$st" || _rc=1 -fi +echo "======== fx_probe(扩展段) ========" +run_suite 120 "$TMP/flx.log" res://probe/fx_probe.tscn +st=$? +grep -E "\[fx_probe\]" "$TMP/flx.log" | tail -2 +verdict "fx_probe" "$TMP/flx.log" "$st" || _rc=1 echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" diff --git a/tools/render_gate.sh b/tools/render_gate.sh index 00db59da..4cb690b6 100755 --- a/tools/render_gate.sh +++ b/tools/render_gate.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # 渲染闸正主——窗口化自动闸。 # 范围(渲染五闸的机器面:shot_probe/golden_compare/board_shot 三件入闸; -# fx_probe 逻辑层归 probe_gate --full;1920 档布局机检面待补)。 +# fx_probe 逻辑层归 03 场景探针闸;1920 档布局机检面待补)。 # 骨架复用 probe_gate:①每段 timeout 守卫(挂死=FAIL 不静默)②段前预清+段后清本 game 目录孤儿 # Godot(按命令行精确匹配本 game 路径,不误杀其他 worktree 会话)③末尾残留清点=批末闸(必须 0)。 # 段: diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index 3607368f..978fc223 100755 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # 验证闸唯一入口(全部验证闸的清单与域划分)—— # 闸清单的唯一定义处。文档只写「验证跑本脚本」,禁在文档里另列闸件清单(那正是 iter27 -# 漏跑 probe_gate --full 的成因:清单散落三份文档、彼此不一致、且无机器校验)。 +# 漏跑场景探针扩展段的成因:清单散落三份文档、彼此不一致、且无机器校验)。 # # 跑完写回执 game/tests/.gates/receipt.json(commit+内容树哈希+各闸退出码+存量基线比对+时间戳); # 强制验证点=合回 master 后 post-merge 钩子推送触发的远端跑批;远端结果用 tools/ci_check.sh 现查(只呈报、 @@ -26,7 +26,7 @@ # 与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变; # 闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以全量为兜底。 # **域划分只在本脚本里定义一处**——文档只写「按改动域跑本脚本」,禁另列闸清单(清单散落多处 -# 正是既往漏跑 probe_gate --full 的成因)。 +# 正是既往漏跑场景探针扩展段的成因)。 # # 判定口径:每道闸与 game/tests/.gates/baseline.json 里的存量基线比对—— # 基线绿 + 现绿 → 绿 @@ -410,7 +410,7 @@ if [ $NONBEHAVIOR -eq 1 ]; then NAMES+=("03 场景探针"); EXITS+=("SKIP"); LOGS+=("") echo ""; echo "[gates] 03 场景探针 → SKIP(影响面选闸:测量面改动不触场景树与探针断言面)" else - gate "03 场景探针" "probe.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --click --full + gate "03 场景探针" "probe.log" bash "$REPO/tools/probe_gate.sh" "$GAME" fi # ── 5. 渲染面(窗口化,依赖 PIL)── if [ $QUICK -eq 1 ] || [ $NONBEHAVIOR -eq 1 ]; then From 8d7274546600b90a606fd1aeac5f0c311fb8c01a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 08:30:05 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E7=BC=96=E6=8E=92(=E8=BF=AD=E4=BB=A352):?= =?UTF-8?q?=20=E4=B8=A4=E5=8D=A1=E7=BC=96=E5=85=A5=E2=80=94=E2=80=94?= =?UTF-8?q?=E2=91=A0=E6=9E=B6=E6=9E=84=E5=AE=A1=E6=9F=A5presentation?= =?UTF-8?q?=E5=B1=82=E5=B0=81=E8=A3=85=E4=BF=AE=E5=A4=8D=EF=BC=884.125?= =?UTF-8?q?=EF=BC=8C=E5=85=88=E8=A1=8C=EF=BC=9A=E9=9B=B6=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E9=87=8D=E6=9E=84=E5=85=88=E8=90=BD=EF=BC=8C?= =?UTF-8?q?=E5=B0=81=E5=8D=B0=E5=8D=B0=E8=AE=B0=E5=B1=82=E4=BB=B6=E5=BB=BA?= =?UTF-8?q?=E5=9C=A8=E9=87=8D=E6=9E=84=E5=90=8E=E5=85=AC=E5=BC=80=E9=9D=A2?= =?UTF-8?q?=EF=BC=89=E2=91=A1=E5=B0=81=E5=8D=B0=E5=8D=B0=E8=AE=B0=E6=B3=95?= =?UTF-8?q?=E9=98=B5=E5=8C=96=EF=BC=883.75=EF=BC=8C=E6=AE=BF=E5=90=8E?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E5=90=88=E8=AE=A1=E2=89=887.875=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=EF=BC=885-10=E5=B8=B8=E8=A7=84=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E5=85=8D=E7=89=B9=E6=89=B9=EF=BC=89=EF=BC=9B=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=E6=B8=85=E5=8D=95=E4=B8=A4=E6=9D=A1=E7=9B=AE=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=8E=A8=E8=BF=9B=EF=BC=8B=E5=B7=B2=E7=BC=96=E8=BF=AD?= =?UTF-8?q?=E4=BB=A352=EF=BC=88=E5=B7=B2=E5=86=99=E5=8D=A1=E6=AE=B5?= =?UTF-8?q?=E4=BF=9D=E7=95=99=EF=BC=89=EF=BC=9Broadmap=E8=BF=AD=E4=BB=A352?= =?UTF-8?q?=E8=A1=8C=E5=BE=85=E5=BC=80=E5=B7=A5=EF=BC=88=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E5=88=97=3D=E6=89=A7=E8=A1=8C=E9=A1=BA=E5=BA=8F=EF=BC=89?= =?UTF-8?q?=EF=BC=8B=E5=BD=93=E5=89=8D=E4=BD=8D=E4=B8=8B=E4=B8=80=E6=AD=A5?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3=E3=80=81CI=E8=AF=BB=E6=95=B0=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E4=B8=BArun=2059=E7=BB=BF=EF=BC=883c264160=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E4=BB=B6=E6=A0=B8=EF=BC=9A=E4=B8=A4=E5=8D=A1=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E4=BB=85=E8=90=BDroadmap/=E5=BE=85=E5=8A=9E=E6=B8=85?= =?UTF-8?q?=E5=8D=95=EF=BC=8C=E6=A0=87=E9=A2=98=E3=80=8C=E5=BE=85=E7=BC=96?= =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E3=80=8D=E5=B1=9E=E7=99=BB=E8=AE=B0=E5=85=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=8D=E5=8A=A8=EF=BC=9Bpipeline=5Fgate=20?= =?UTF-8?q?PASS=EF=BC=8827=E6=9D=A1=E7=9B=AE/52=E6=80=BB=E8=A1=A8=E8=A1=8C?= =?UTF-8?q?/=E4=BA=A4=E5=8F=89=E6=A0=B82=E9=93=BE/=E5=8D=A1=E7=89=877?= =?UTF-8?q?=E5=BC=A0=EF=BC=89=EF=BC=9Brun=5Fall=5Fgates=20--docs=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E9=80=9A=E8=BF=87=E9=9B=B6=E6=96=B0=E5=A2=9E=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roadmap.md | 5 +++-- 待办清单.md | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/roadmap.md b/roadmap.md index 3f2db098..b8f4d69e 100644 --- a/roadmap.md +++ b/roadmap.md @@ -5,8 +5,8 @@ ## 当前位(只回答「现在该做什么」;三行内随批次换代,禁止在此扩写成状态快照) - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 -- **迭代位**:迭代 1–51 全部已完成——迭代 51(四卡:EffectPort 蓄力层删键/架构审查 5.1 封装修复/P2 NODE_TYPES 词表单点化/L1 深度 3 混编)2026-11-04 合回(合并提交 fb27dc2f,独立验收审计终判通过),批后收尾闭合(2026-11-04):反哺点十条落文 architecture.md(第十一条落点不存在跳过、呈报在案)+S13 无凭证基线键经用户裁撤销(--refresh 重生成入口同批删除、基线改人工修订,gdstyle 红在案由还债 P0 追绿)+还债与死函数清退两条 P0 登记+presentation 卡未编迭代迁出承接条目+产出物退役三件+待办三条目删除。迭代 50 收尾记录见 git 提交 47c752ea,迭代 49 见 aa396eb9。远端 CI:run 51/52 红(runner 中断非闸红)、run 53–55 排队待 Mac 上线。 -- **下一步**:P0 两条待写卡(minesweeper_rules 公开面还债——gdstyle 红在案追绿;EffectEngine 死函数与全仓孤儿函数清退);presentation 层封装修复卡已写卡待编迭代(4.125 工作日)。待编池:改写族孤儿(待处置方向裁决)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 +- **迭代位**:迭代 1–51 全部已完成——迭代 51(四卡:EffectPort 蓄力层删键/架构审查 5.1 封装修复/P2 NODE_TYPES 词表单点化/L1 深度 3 混编)2026-11-04 合回(合并提交 fb27dc2f,独立验收审计终判通过),批后收尾闭合(2026-11-04):反哺点十条落文 architecture.md(第十一条落点不存在跳过、呈报在案)+S13 无凭证基线键经用户裁撤销(--refresh 重生成入口同批删除、基线改人工修订,gdstyle 红在案由还债 P0 追绿)+还债与死函数清退两条 P0 登记+presentation 卡未编迭代迁出承接条目+产出物退役三件+待办三条目删除。迭代 50 收尾记录见 git 提交 47c752ea,迭代 49 见 aa396eb9。远端 CI 现查:master run 59 绿(commit 3c264160,跑闸步真跑过)。 +- **下一步**:迭代 52 已编入待开工(两卡 ≈7.875 个工作日,`/iter 52` 发令)。P0 两条待写卡(minesweeper_rules 公开面还债——gdstyle 红在案追绿;EffectEngine 死函数与全仓孤儿函数清退)。待编池:改写族孤儿(待处置方向裁决)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 ## 里程碑时间线 @@ -85,6 +85,7 @@ | 迭代 49 | 已完成(2026-11-02) | 已退役(git 历史可考) | 2026-11-02 | ≈7.25 个工作日 | 已退役(git 历史可考) | | 迭代 50 | 已完成(2026-11-03) | 已退役(git 历史可考) | 2026-11-03 | ≈12.875 个工作日〔用户特批 2026-11-03 超 10 个工作日〕 | 已退役(git 历史可考) | | 迭代 51 | 已完成(2026-11-04) | 已退役(git 历史可考) | 2026-11-04 | ≈6.625 个工作日 | 已退役(git 历史可考) | +| 迭代 52 | 待开工 | reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md
reports/需求卡片-封印印记法阵化批-2026-11-04.md | 2026-11-04 | ≈7.875 个工作日 | — | - 迭代 38 收口为带红合并〔用户特批 2026-10-26〕:09 静态扫描 S13 gdstyle 恶化 2 条经批准记入基线存量红(git 提交 c4c69701),卡② 停卡退上游,处方已于 2026-10-26 修订定稿 v1.3(git 提交 b3ea7532),随「gdstyle 存量债全量清偿」条目波 7 待编排。 - 迭代 39 收口为带红合并〔用户特批 2026-10-27〕:02 EV 锚 goblin 0.9148∉[1.00,1.20](触发器批有意行为改动,fp f74b9a48ef4a)已随 EV 锚重锚批换带归绿(git 提交 ba5105aa);08d 生涯.md:55 行号漂移已随 2026-10-28 批后收尾反哺落文修复归绿。三卡拆分后终局 gdstyle 恶化 2 条(battle_orchestrator max-file-length、level_map_screen max-class-variables)归迭代 40 卡①还债与迭代 41 存量债序列。 diff --git a/待办清单.md b/待办清单.md index 5c91e57d..e8b4c481 100644 --- a/待办清单.md +++ b/待办清单.md @@ -178,7 +178,7 @@ **改动面**:文档+美术素材+游戏代码 -**状态**:美术设计已闭合+已写卡 +**状态**:美术设计已闭合+已写卡+已编迭代 52 #### 教学形态未定(2026-10-04 登记,来源=审计发现) @@ -277,4 +277,4 @@ **改动面**:游戏代码 -**状态**:文档设计已闭合+已写卡 +**状态**:文档设计已闭合+已写卡+已编迭代 52 From cea1f382692d6c7cfb67eb5d1ed862515eb74a86 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 14:37:22 +0800 Subject: [PATCH 18/19] =?UTF-8?q?=E5=86=99=E5=8D=A1(P0=E4=B8=A4=E5=8D=A1+?= =?UTF-8?q?=E6=96=B0=E7=99=BB=E8=AE=B0=E4=B8=80=E6=9D=A1):=20minesweeper?= =?UTF-8?q?=5Frules=E5=85=AC=E5=BC=80=E9=9D=A2=E8=BF=98=E5=80=BA=E6=89=B9(?= =?UTF-8?q?safe=5Fremaining=E7=A7=81=E6=9C=89=E5=8C=96=E6=94=B6=E6=95=9B21?= =?UTF-8?q?=E2=86=9220=E9=9B=B6=E5=9F=BA=E7=BA=BF=E9=94=AE;=20settle=5Fbat?= =?UTF-8?q?ch/fx=5Freveal=E5=8F=97=E6=9E=B6=E6=9E=84=E5=9C=A8=E5=86=8C?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=E4=BF=9D=E6=8A=A4=E6=8E=92=E9=99=A4=E3=80=81?= =?UTF-8?q?preview=5Freveal=E4=B8=BA=E5=9C=A8=E5=86=8Csim=E5=87=BA?= =?UTF-8?q?=E5=8F=A3=E4=BF=9D=E7=95=99=E2=80=94=E2=80=9421=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=85=A8=E9=87=8F=E7=9B=98=E7=82=B9=E8=A1=A8=E5=9C=A8?= =?UTF-8?q?=E5=86=8C)=20+=20=E5=AD=A4=E5=84=BF=E5=87=BD=E6=95=B0=E6=B8=85?= =?UTF-8?q?=E9=80=80=E6=89=B9(=E5=88=A4=E6=AD=BB=E5=85=AD=E4=BB=B6:=20has?= =?UTF-8?q?=5Feffect=5Fon/=5Fbp=5Fread=5Fint/=5Ffind=5Ftoken=E6=AD=BB?= =?UTF-8?q?=E5=8C=85=E8=A3=85/has=5Fopen/set=5Ffeedback=5Fstate/stop=5Fres?= =?UTF-8?q?olved;=20=E7=BA=A7=E8=81=94=E6=A0=B8=E5=88=97=E9=80=90=E4=BB=B6?= =?UTF-8?q?=E7=95=99=E5=AD=98=E5=A4=8D=E6=A0=B8;=20B=E6=AE=B569=E4=BB=B6?= =?UTF-8?q?=E6=B5=8B=E9=87=8F=E9=9D=A2=E6=8C=89=E7=99=BB=E8=AE=B0=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E4=BF=9D=E7=95=99=E5=85=A8=E5=88=97=E9=99=84=E8=A1=A8?= =?UTF-8?q?)=20+=20=E5=BE=85=E5=8A=9E=E6=B8=85=E5=8D=95:=20=E4=B8=A4P0?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E5=BE=85=E5=86=99=E5=8D=A1=E2=86=92=E5=B7=B2?= =?UTF-8?q?=E5=86=99=E5=8D=A1=E5=AE=9E=E5=86=B5=E6=8D=A2=E4=BB=A3=20+=20?= =?UTF-8?q?=E6=96=B0=E7=AB=8Bconfig=5Fdb.=5Fvalidate=5Fskill=5Fpool=5Flink?= =?UTF-8?q?s=E6=97=A7=E6=A0=A1=E9=AA=8C=E4=BB=B6=E6=9D=A1=E7=9B=AE(?= =?UTF-8?q?=E5=A4=84=E7=BD=AE=E6=96=B9=E5=90=91=E5=BE=85=E8=A3=81=E6=B3=A8?= =?UTF-8?q?=E8=BF=9B=E5=AE=9E=E5=86=B5);=20=E4=B8=83=E6=9F=A5=E8=87=AA?= =?UTF-8?q?=E5=AE=A1=E8=BF=87+grilling=E9=9B=B6=E5=BE=85=E8=A3=81+pipeline?= =?UTF-8?q?=5Fgate=20PASS(28=E6=9D=A1=E7=9B=AE10=E5=8D=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...‰‡-minesweeper_rules公开面还债批-2026-11-04.md | 179 +++++++++++++ reports/需求卡片-孤儿函数清退批-2026-11-04.md | 251 ++++++++++++++++++ 待办清单.md | 14 +- 3 files changed, 441 insertions(+), 3 deletions(-) create mode 100644 reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md create mode 100644 reports/需求卡片-孤儿函数清退批-2026-11-04.md diff --git a/reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md b/reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md new file mode 100644 index 00000000..71164beb --- /dev/null +++ b/reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md @@ -0,0 +1,179 @@ +# 需求卡片:minesweeper_rules 公开面还债批 + +> 来源:待办清单「minesweeper_rules 公开面还债——S13 超标键撤销后的清偿」(2026-11-04 登记,来源=批次移交,优先级=P0;需求性工程条目,证据指认在条目实况)。**唯一事实源:该条目实况证据链+本卡第 3 节 2026-11-04 代码级考证(21 公开方法全量调用方盘点,逐方法实查)。** +> 裁决沿革:登记方向=「settle_batch/fx_reveal 族收敛或搬迁」(批次移交在册);2026-11-04 本会话全量盘点后按证据收窄——两族受架构在册边界与职责轴保护不可作收敛点(§3.2),收敛点唯一化=`safe_remaining`(无生产消费方公开面收回,§3.3),待用户批卡背书。 +> 验收档:中 +> 版本:v1.0(2026-11-04) + +## 1. 目标与完成定义 + +把 S13 红键清偿为绿: + +- `game/core/minesweeper_rules.gd` 公开方法 21→20:`safe_remaining` 公开→私有 `_safe_remaining`(函数体逐字不动); +- gdstyle 闸(S13=`quality/max-public-methods`,默认上限 20)该文件归绿,**零新增基线键**; +- 游戏行为零变化:私有化=改名,无任何运行期可观测面变化;4 处测试直连点改锚私有面,断言消息与期望值逐字不变。 + +**完成定义**:gdstyle 单件读数公开方法 20≤20 且零红键;`minesweeper_rules_test` 既有断言全数通过;全仓 `\.safe_remaining\(` 调用形态零残留;`--code` 域全套闸绿。 + +## 2. 设计事实源 + +需求性工程条目(重构/还债,无设计线):设计事实源=**证据指认**——待办清单条目实况证据链(`tools/gdstyle_baseline.txt` 该文件零在册键=S13 键 2026-11-04 用户裁撤销后为活红;git 提交 7f512aa8 归位两法在案)+本卡第 3 节 2026-11-04 代码级复核(file:line 逐条实查)。 + +## 3. 现状事实(2026-11-04 代码级考证) + +### 3.1 红键本体 + +- gdstyle S13 规则=`quality/max-public-methods`,默认上限 20(`tools/gdstyle.toml` 头注:阈值取 gdstyle 默认值)。 +- `game/core/minesweeper_rules.gd` 非下划线公开方法实数 21(`_init` 豁免,与 S9 R3 同口径)::52/:58/:70/:81/:85/:89/:93/:97/:101/:109/:116/:129/:134/:146/:175/:180/:239/:379/:397/:487/:554。 +- `tools/gdstyle_baseline.txt` 该文件零在册键(grep 零命中)——S13 键经用户裁撤销后,红为 NEW 形态活红。 + +### 3.2 登记方向考证——settle_batch/fx_reveal 两族为何不可作收敛点 + +1. **settle_batch**(:239)=架构在册**唯一提交入口**(architecture.md:105「A2 行」);生产调用方=组合根 `main.gd:574`/:603 两处。搬迁=改架构在册 API 边界;收敛(合并/缩签名)=改唯一提交口契约,零职责收益。 +2. **fx_reveal**(:379)=**棋盘状态本体写口**:函数体改已开集 `_revealed`、发 `cell_revealed`(:380-392);消费方=core 件 `battle_fx_host.gd:185`、`reward_grant.gd:176`。按职责轴(code-conventions §1.1 拆分判据①)是本类本职——搬出=状态暴露或连锁闭包复制(违唯一面)。 + +### 3.3 21 公开方法全量调用方盘点(2026-11-04) + +排查语料=`game/` 全部 .gd 与 .tscn 非注释行;下表「生产调用方实证」=调用形态实查(接收者为 minesweeper 实例)。 + +| # | 方法(定义行) | 生产调用方实证 | 测量/测试面 | 处置 | +|---|---|---|---|---| +| 1 | current_board(:52) | main.gd:375/:582、board_resolvers.gd:30/:44、battle_orchestrator.gd:701 | sim:7 probe:16 test:52 | 保留 | +| 2 | harmful_variants(:58) | grant_segment.gd:110 | — | 保留 | +| 3 | harmful_adjacent(:70) | grant_segment.gd:359 | — | 保留 | +| 4 | reveal_record(:81) | board_view.gd:494/:564 | sim:1 probe:1 test:1 | 保留 | +| 5 | hero_pos(:85) | main.gd:588 | sim:6 probe:19 test:45 | 保留 | +| 6 | is_revealed(:89) | board_view.gd:303/:475/:490/:535、battle_fx_host.gd:177 | sim:10 probe:23 test:22 | 保留 | +| 7 | has_seal(:93) | reward_grant.gd:174 | sim:7 probe:11 test:7 | 保留 | +| 8 | revealed_cells(:97) | battle_orchestrator.gd:507/:700 | sim:3 probe:1 test:16 | 保留 | +| 9 | **safe_remaining(:101)** | **生产外部零调用**——唯一生产调用=同类 clear_map_cleared(:110) | test:4 | **收敛点:公开→私有** | +| 10 | clear_map_cleared(:109) | main.gd:582 | test:1 | 保留 | +| 11 | remove_mines_in(:116) | reward_grant.gd:187、grant_segment.gd:400 | — | 保留 | +| 12 | is_removed(:129) | board_view.gd:303/:537 | test:3 | 保留 | +| 13 | has_unrevealed(:134) | treasure_interact_controller.gd:123 | test:1 | 保留 | +| 14 | install_board(:146) | board_installer.gd:50 | test:6 | 保留 | +| 15 | seal_count(:175) | battle_orchestrator.gd:508/:701 | test:9 | 保留 | +| 16 | retire_board(:180) | battle_orchestrator.gd:534、battle_refresh_flow.gd:69 | — | 保留 | +| 17 | settle_batch(:239) | main.gd:574/:603 | sim:3 probe:8 test:134 | 保留(§3.2-1) | +| 18 | fx_reveal(:379) | battle_fx_host.gd:185、reward_grant.gd:176 | test:13 | 保留(§3.2-2) | +| 19 | preview_reveal(:397) | 生产零调用——**架构在册 sim 信息面出口**(architecture.md:105;sim/auto_player.gd:155 消费) | test:6 | 保留(改它必破坏 MC 自动玩家读数连续性) | +| 20 | compute_move_path(:487) | board_hover.gd:122+同类 _do_move(:479) | sim:3 probe:1 test:5 | 保留 | +| 21 | mine_total(:554) | battle_orchestrator.gd:384 | test:2 | 保留 | + +**结论**:20 个公开方法有生产调用方(全数保留);`preview_reveal` 生产零调用但为架构在册 sim 出口(保留);`safe_remaining` 生产外部零调用(唯一外部消费面=测试直连)——公开面收回的唯一合法收敛点。 + +### 3.4 safe_remaining 本体 + +- 函数体(:101-106):数未揭示安全格(遍历 `_board.safe_cells()` 对 `_revealed` 求余量)。 +- 消费面:同类 `clear_map_cleared`(:110 `return safe_remaining() == 0`)+测试直连 4 组断言 5 处调用表达式(`minesweeper_rules_test.gd` :56/:89/:93 两处/:94——精确余量断言,无公开可观测面承载精确计数:`clear_map_cleared` 仅 bool)。 +- 在案观察(不收件):`_classify`(:276-280)有一段内联余量计数,与 `safe_remaining` 形态重复——合并无职责依据,不动。 + +## 4. 设计契约(🔴 违反即 L3 停机呈报) + +1. 🔴 **游戏行为零变化**——私有化=改名,函数体逐字不动;零 RNG/信号/存档/表值触面。 +2. 🔴 **收敛动机纪律**——本批收敛对象=「无生产消费方的公开面」收回(职责依据,与死码清退同族);禁以阈值数字为动机合并/收拢/搬迁任何有消费方的公开面(code-conventions §1.1 拆分判据③);禁动 settle_batch/fx_reveal/preview_reveal(§3.2/§3.3 在册理由)。 +3. 🔴 **禁保留无效成员**——私有化后禁留公开别名/转发壳/空 getter(code-conventions §1.1 拆分判据⑤)。 +4. 🔴 **测试断言零削弱**——4 组断言改锚后检查语义与期望值逐字不变;改锚=内部面直查**受控例外**(test-conventions §8.2:精确余量计数无公开可观测面可钉),对照表该行注明理由。 +5. 🔴 **MC/模拟零跑**——无数值改动;sim/auto_player 零触碰。 +6. 🔴 **实现批禁写 `design/` 整个目录与根规则文件**——architecture.md:105 走反哺点清单由文档批落文。 +7. 🔴 **车道铁律**——实现走独立 worktree,主区只作合并落点。 + +## 5. 范围边界 + +**做**:`safe_remaining`→`_safe_remaining` 改名(1 处定义+1 处内部调用点+测试 5 处调用表达式改锚)。 + +**不做**: +- 其余 20 公开方法零触碰(§3.3 盘点表逐行在册理由); +- `tools/gdstyle_baseline.txt` 零编辑(不加键不改键——21→20 低于默认上限自然归绿;基线维护归人工修订经用户批准,本批无需); +- `_classify` 内联计数与 `safe_remaining` 的形态重复不并(§3.4 在案观察); +- sim/probe 零触碰。 + +**明示不收件的去向**:无。 + +## 6. 关联文档同步面 + +| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 | +|---|---|---| +| `design/engineering/architecture/architecture.md` | :105「A2 行」句「公开方法面 19→21(超阈值贴顶——还债归待办清单 P0 条目…)」本批落地后过期 | 实现批禁写——登记进反哺点清单(改现状句:公开方法面 20,safe_remaining 私有化,S13 归绿),用户裁决后归文档批落文 | +| `game/tests/README.md` | :118-121 排雷玩法四能力行——代表用例均为 `_test_*` 函数名,本批零改名 | 实现批禁写——批末收尾核对名实一致(零回表预期) | +| 术语表/GDD 各系统文档 | 零关联(纯代码面收敛) | 无同步义务 | +| 待办清单 | 本条目状态推进(已写卡→已编迭代→完成删除) | 流水线各段承载,实现批不碰 | + +## 7. 任务卡 + +**卡前文件结构图**(零新增文件): + +| 文件 | 职责与改动 | 行为改动 | +|---|---|---| +| `game/core/minesweeper_rules.gd` | :101 定义改名+:110 调用点同步(函数体逐字不动) | 否(非行为改动——对照表行 1) | +| `game/tests/unit/core/minesweeper_rules_test.gd` | 5 处调用表达式改锚(断言消息与期望值逐字不动) | 否(适配——申报见第 8 节) | + +### T0 基点读数(≈0.1 工作日) + +- [ ] gdstyle 单件读数固化:`python tools/gdstyle_gate.py` 按闸件用法跑 `game/core/minesweeper_rules.gd`——记录红键原文(NEW:公开方法 21)与公开方法计数 +- [ ] 测试断言基线固化:`minesweeper_rules_test.gd` :55-56/:89-94 四组断言现文抄录进实现报告(改锚前后逐字比对锚) + +### T1 私有化与测试改锚(≈0.25 工作日) + +- [ ] `minesweeper_rules.gd:101`:`func safe_remaining() -> int:` → `func _safe_remaining() -> int:`(:102-106 函数体逐字不动) +- [ ] `minesweeper_rules.gd:110`:`return safe_remaining() == 0` → `return _safe_remaining() == 0` +- [ ] `minesweeper_rules_test.gd`::56/:89/:93(两处)/:94 调用目标 `_rules.safe_remaining()` → `_rules._safe_remaining()`——断言消息串与期望表达式逐字不动 +- [ ] 孤立验证:`…console.exe --script res://tests/run_all.gd` 单测域全绿(重点 minesweeper_rules_test) + +### T2 验证与落位(≈0.15 工作日) + +- [ ] gdstyle 单件绿:公开方法 20≤20,该文件零红键 +- [ ] 零残留 grep:全仓 `\.safe_remaining\(` 零命中;`\._safe_remaining\(` 恰 6 处(:110+测试 5 处) +- [ ] 里程碑步出口:`tools/run_all_gates.sh --code` 全绿 +- [ ] 产出物落位:`reports/iter-实现报告-<日期>.md`(各卡状态+受控例外注记+实现决定记录;无实机目检项——改名零玩家可观测提交面)+`reports/iter-反哺点-<日期>.md`(architecture.md:105 一条) + +**估时**:合计中值 ≈0.5 个工作日(区间 0.4–0.6)。 + +## 8. 验证闸(改动—验收对照表) + +| # | 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---|---| +| 1 | 公开面收敛 21→20(S13 归绿) | `minesweeper_rules.gd:101/:110` 改名 | gdstyle S13 读数:该文件 NEW 红→零红(20≤20)——落实现报告 | 静态闸 | gdstyle 闸单件直跑 | S13 公开方法计数 21→20=本批特有提交面 | 既有:装板与开局免费展开(封锁格无资源、计入清图) | +| 2 | 行为零变化回归锚 | 同行 1(改名) | `minesweeper_rules_test.gd` 既有断言全数通过(_test_install_invariants/_test_reveal_advances 等,当轮跑批执行) | 单元 | `tests/run_all.gd` 单元套件 | 断言期望值零变化(改名后同值同断言)=零行为漂移证据 | 既有:装板与开局免费展开(封锁格无资源、计入清图) | +| 3 | 测试直连改锚(受控例外) | `minesweeper_rules_test.gd` :56/:89/:93/:94 | 同行 2(改锚后断言在当轮真实跑批执行并通过) | 单元 | 同行 2 | 精确余量计数断言——**受控例外注记**:精确余量无公开可观测面可钉(`clear_map_cleared` 仅 bool),理由随本表申报 | 既有:装板与开局免费展开(封锁格无资源、计入清图) | + +- **能力行覆盖说明**:本批测试函数名零变化——README:118-121 四能力行(装板与开局免费展开/挖/封印合法性与可辨拒绝/棋盘揭示预览只读口/封印不可逆与挖开封印格放行)的用例全部照旧运行;对照表三行选「装板与开局免费展开」为锚(其代表用例 `_test_install_invariants` 直含改锚断言 :56),其余三行由同套件当轮跑批兜底。 + +- **非行为改动行**:私有化改名=非行为改动(判断依据:函数体逐字不动+行 2 断言全绿)。 +- **测试方式两分法**:本批全部归调试接口验证(单元断言+静态闸读数);**无用户目检项**(零玩家可观测提交面)。 +- **测试削弱申报**:测试调用点 5 处改锚——断言期望值与检查语义逐字零变化;无断言删除、无阈值放宽、无扫描面缩小。 +- **独立验收审计**:`reports/iter-验收审计-<日期>.md`,未参与实现的会话或子代理执行,未过不得合并(test-conventions §8.7;验收档中=对照表全量核对+判据绑定面抽查+复跑以远端 CI 现查读数代证)。 +- **回归措辞**:里程碑步(T2)出口跑一次 `--code` 域全套闸;卡内修复只做出红最小单元的最窄确认。 + +## 9. 异常分级 + +- **L1 自救**:断言消息占位符顺序微调、空行排版。 +- **L2 记录继续**:措辞级微调(记录进实现报告「实现决定记录」)。 +- **L3 停机呈报**:①改名后 S13 仍红(计数口径与本卡考证不符=上游缺陷,停卡退回写卡段);②发现 `safe_remaining` 生产外部调用方(与 §3.3 盘点矛盾);③机检键与本卡契约互斥(禁改闸件豁免、禁自造第三条路);④全套闸红不可最小单元收敛。 + +## 10. 代拍板白名单 + +1. 断言消息串中随改名联动的参数占位写法(保持文案语义)。 +2. 删除/空行排版微调。 + +## 11. 待裁点 + +零待裁点——收敛点按 §3.3 盘点证据唯一化(safe_remaining);登记方向(settle_batch/fx_reveal 族)的排除分析在册(§3.2);用户批卡即对收窄方向背书。开工门=grilling 复核+用户裁决+迭代编排(编入迭代后估时总量 <5 工作日=冻结待凑批)。 + +## 12. 落位与可复用性预检 + +- **复用面**:零新增件——纯改名;禁新增任何转发壳(契约 3)。 +- **唯一面**:`_safe_remaining` 唯一落点仍在 `minesweeper_rules.gd`(数剩余安全格=棋盘状态本职,不迁移);grep 同名核查已证 `safe_remaining`(被私有化)与 `safe_cells`(BoardData 方法,另一标识符域)无混淆。 +- **分层与交互面**:零跨层变化——生产调用方只有同类内部一点(:110);测试直连走受控例外(契约 4);Core 零 UI 依赖不因改名新增绕行;零表值触面。 + +## 13. 禁为机检文本保留无效成员 + +🔴 适用——禁为迎合 S13 消息文本(含计数)保留或新增无效成员/别名/转发壳;改名与机检键互斥时实现段一律停卡呈报,不得自造第三条路。 + +## 14. 实现规范预算 + +零新增文件——三查不适用。存量触面:`minesweeper_rules.gd` 基线零在册键(grep 已证)——S13 预期 NEW 红→消失(21→20);改名不改函数长/分支/返回数(gdstyle 函数级规则零漂移);测试文件不在 S13 扫描面(产品码三目录外,code-conventions §4.13)。全部触面 S13 键较登记值只降不升(T0/T2 读数步骤承载)。 + +## 15. 拆分边界纪律 + +本卡为收敛类(公开面收回)——**无拆出单元**(私有化非拆分,无新件)。红线:收敛动机=职责依据(无生产消费方公开面,§3.3 盘点表为证),非阈值数字;禁为凑 20 合并/收拢任何有消费方的公开面;禁把本卡与卡②清退面混装(两卡两事项,编排段可凑批)。 diff --git a/reports/需求卡片-孤儿函数清退批-2026-11-04.md b/reports/需求卡片-孤儿函数清退批-2026-11-04.md new file mode 100644 index 00000000..5b2527ed --- /dev/null +++ b/reports/需求卡片-孤儿函数清退批-2026-11-04.md @@ -0,0 +1,251 @@ +# 需求卡片:孤儿函数清退批 + +> 来源:待办清单「EffectEngine 死函数与全仓孤儿函数清退」(2026-11-04 登记,来源=会话提及,优先级=P0;需求性工程条目,证据指认在条目实况)。**唯一事实源:该条目实况证据链+本卡第 3 节 2026-11-04 全仓排查考证(排查总体/口径/判死清单逐件实查)。** +> 裁决沿革:删除方向 2026-11-04 用户裁(删 `has_effect_on` 并全仓排查生产码零调用死函数一并清退);排查口径按登记(生产码为排查总体;测试直连与受控例外面不判死)。 +> 验收档:中 +> 版本:v1.0(2026-11-04) + +## 1. 目标与完成定义 + +产品码三目录孤儿函数清零: + +- 判死清单六件(§3.2)零调用方死函数删除(含各自专属文档注释行); +- 删除后级联零新增孤儿(关联在役成员逐件留存复核); +- 游戏行为零变化(六件全为无调用方死件,删除无运行期可观测面)。 + +**完成定义**:六件定义行消失且全仓(.gd+.tscn)零残留;级联留存复核通过(§3.2 级联核列);既有套件全绿;`--code` 域全套闸绿。 + +## 2. 设计事实源 + +需求性工程条目(死码清退,无设计线):设计事实源=**证据指认**——待办清单条目实况(`game/core/effect_engine.gd:204` `has_effect_on` 全仓零调用方,登记证据;2026-11-04 用户裁删并全仓排查)+本卡第 3 节 2026-11-04 全仓排查复核(file:line 逐件实查)。 + +## 3. 现状事实(2026-11-04 全仓排查考证) + +### 3.1 排查口径与方法(可复现) + +- **排查总体**=`game/core`+`game/data`+`game/presentation` 全部 `func` 定义(code-conventions §4.13 产品码三目录口径;`game/` 根目录无散落 .gd——find 已证)。 +- **语料**=`game/` 下全部 .gd 与 .tscn 的非注释行(.tscn 覆盖信号接线 `method=` 形态)。 +- **判死**=全仓零调用方——测试/sim/probe 直连不判死(登记口径);引擎虚函数(`_init`/`_ready`/`_process` 等)除外。 +- **方法**:逐函数名全仓标识符检索(词边界)→ 剥注释 → 定义行外零出现=死函数候选 → 逐件人工复核调用形态(排除字符串标签/跨类同名噪声;同名多定义清单已核,六件名均无跨类同名定义)。 + +### 3.2 判死清单(六件) + +| # | 件 | 定义(含注释) | 职责 | 零调用证据 | 级联核(保留面) | +|---|---|---|---|---|---| +| 1 | `has_effect_on` | effect_engine.gd:203-212 | 订阅查询面(发射点缺省行为仲裁——round_end 走 RAP 转存后成死件) | 登记证据+本次全仓 grep 仅定义命中 | `_triggers`/`dispatch`(:218)在役不受影响 | +| 2 | `_bp_read_int` | breakpoint_store.gd:211-219 | 数值类读侧 helper(clamp/params_fallback 策略分支) | 全仓零调用;生产读侧=`bp_read`(:197,消费 keep_on_null/plain) | `_bp_strategy`(:207)由 `bp_read` 共用——保留;`BREAKPOINT_FIELDS` 的 clamp/params_fallback 策略标签保留(文档标签;其机械消费面缺失在案观察,见 §3.3 注④) | +| 3 | `_find_token` | adventure_runner.gd:250-252 | 死包装——转发 `_curio.find_token`,而 curio_stop 无此方法(若被调用即运行期错误);真件=`curio_deck.find_token`(curio_deck.gd:51,curio_stop.gd:107/:229 在役消费) | 全仓零调用 | `curio_deck.find_token` 保留(活) | +| 4 | `has_open` | modal_stack.gd:56-62 | 开态查询(组合根输入锁判据位) | 全仓零调用——组合根实际消费 `has_open_excluding_tags`(空标签集=全计,同形;:65-67 注释自证) | 独立谓词,零级联 | +| 5 | `set_feedback_state` | camp_scene_view.gd:88-92 | 持久基态外部设置口 | 全仓零调用——视图内 `_handle_motion` 族自持(`_apply_state` :113/:116/:118/:127/:133 在役) | `_apply_state`/`_base_state`/`_base_states` 保留 | +| 6 | `stop_resolved` | curio_stop.gd:303-305 | 放行谓词(宿主关序受理) | 全仓零调用 | `_resolved` 多读写在役(:207/:215/:216/:343/:360/:394/:401/:413);`_pending` 由 `curio_stop_pending`(:300)消费——均保留 | + +### 3.3 B 段清单——生产码零调用、仅 sim/probe/tests 出现(登记口径保留,不判死) + +排查另得 69 件(附表全列)+ `_init`/`_ready` 引擎虚函数面(排除)。重点注记: + +- **注①**:`config_db.gd:170` `_validate_skill_pool_links`——被**跨表校验件取代的旧实现**:生产真身=`config_db_cross_validator.gd:197-213`(同判定类别 dangling_skill/pool_composition,检查面更全);旧件生产零调用、测试直连 5 处(round_flow_skill_pool_test.gd:869/871/876/893/905)——测试锚在被取代实现。**去向=新立待办条目**(写卡段同批登记),不在本卡收件。 +- **注②**:`gp_wallet.gd:10` `gain_gp`——生产零调用、测试造数面(hero_ledger_test.gd:105)——保留。 +- **注③**:`main.gd:106` `_start_adventure_from_menu`——**在册中间态**(代码注释自证:探针直调位点 mn10 逐个登记,卡④到位后由大门两分支换代)——非孤儿发现,保留。 +- **注④**:`breakpoint_store.gd` 的 BREAKPOINT_FIELDS 策略标签 `clamp`/`params_fallback`(:19/:20/:39/:40/:41)机械消费面随 `_bp_read_int` 死亡——标签保留为文档语义,是否补消费面或清标签归后续裁决(本卡不动)。 +- 其余 B 段件=探针取证面(`*_probe`/`press_*_for_probe`)、调试面(debug_overlay 族)、测量面(playtest_logger/sim 消费)与测试造数面——按登记口径全部保留。 + +**B 段附表(69 件全列)**: + +| 定义处 | 函数名 | 保留面调用计数 | +|---|---|---| +| presentation/main.gd:106 | _start_adventure_from_menu | probe:11, test:6 | +| core/config_db.gd:170 | _validate_skill_pool_links | test:5 | +| core/slot_store.gd:48 | active_slot | probe:5, test:11 | +| presentation/views/hero_info_panel_view.gd:156 | armor_text | probe:2 | +| core/slot_store.gd:24 | attach_path | probe:3, test:9 | +| core/slot_store.gd:34 | attach_slot_template | probe:4, test:15 | +| presentation/views/hero_info_panel_view.gd:152 | attack_text | probe:3 | +| core/battle_orchestrator.gd:152 | attune_has | test:8 | +| core/level_runner.gd:482 | awaiting_choice | probe:5, sim:1, test:19 | +| presentation/views/level_map_screen.gd:589 | canvas_probe | probe:3, test:1 | +| presentation/views/board_view.gd:442 | cell_center_local | probe:1 | +| presentation/views/board_view.gd:845 | cell_rect_probe | probe:3 | +| presentation/views/board_view.gd:817 | cells_probe | probe:25 | +| presentation/views/board_view.gd:419 | click_cell | probe:7 | +| presentation/views/ap_bar_view.gd:197 | confirm_visible | probe:1 | +| presentation/views/standee_body.gd:355 | content_rect | probe:1 | +| data/schemas/adventure_table.gd:165 | deck_total | test:2 | +| core/config_db.gd:86 | diagnostics | sim:1, test:17 | +| presentation/views/board_view.gd:822 | diggable_probe | probe:8 | +| presentation/views/level_map_screen.gd:595 | display_probe | probe:2, test:1 | +| presentation/views/debug_overlay.gd:69 | effect_text | probe:2 | +| presentation/views/battle_stage_view.gd:262 | enemy_body_at | probe:2 | +| presentation/views/board_view.gd:832 | fog_params_probe | probe:5 | +| presentation/views/battle_end_overlay.gd:382 | fx_probe | probe:10, test:1 | +| presentation/views/standee_body.gd:217 | fx_probe | probe:10, test:1 | +| core/gp_wallet.gd:10 | gain_gp | test:1 | +| presentation/views/camp_scene_view.gd:83 | get_hotspot_rect | probe:1 | +| core/slot_store.gd:53 | has_write_target | test:9 | +| presentation/views/standee_body.gd:304 | hero_state_probe | probe:4 | +| presentation/views/board_view.gd:827 | hover_probe | probe:2 | +| presentation/views/standee_body.gd:57 | intent_probe | probe:4 | +| core/reward_economy.gd:74 | is_mapped | sim:1 | +| presentation/views/level_map_screen.gd:584 | layout_probe | probe:7, test:5 | +| presentation/playtest_logger.gd:45 | log_path | sim:11, test:2 | +| presentation/playtest_logger.gd:118 | log_run | sim:2 | +| presentation/views/standee_body.gd:64 | name_line_probe | probe:1, test:3 | +| core/level_runner.gd:400 | next_candidates | sim:1, test:2 | +| presentation/views/debug_overlay.gd:121 | output_text | probe:1 | +| presentation/views/battle_log_view.gd:102 | parsed_text | probe:2, test:6 | +| presentation/views/standee_body.gd:283 | portrait_probe | probe:2, test:3 | +| presentation/views/debug_overlay.gd:127 | press_apply_for_probe | probe:2 | +| presentation/views/level_map_screen.gd:402 | press_candidate | probe:8, test:1 | +| presentation/views/curio_pick_overlay.gd:157 | press_card | probe:3, test:2 | +| presentation/views/curio_pick_overlay.gd:163 | press_confirm | probe:3, test:1 | +| presentation/views/ap_bar_view.gd:201 | press_confirm_for_probe | probe:1 | +| presentation/views/curio_pick_overlay.gd:167 | press_continue | probe:7, test:1 | +| presentation/views/node_overlay.gd:33 | press_continue | probe:7, test:1 | +| presentation/views/curio_pick_overlay.gd:172 | press_refresh | test:2 | +| presentation/views/ap_bar_view.gd:193 | press_refresh_for_probe | probe:1 | +| presentation/views/debug_overlay.gd:131 | press_reset_for_probe | probe:1 | +| presentation/views/debug_overlay.gd:135 | press_simulate_for_probe | probe:1 | +| core/minesweeper_rules.gd:397 | preview_reveal | sim:1, test:6 | +| core/treasure_items.gd:236 | probe | probe:24, test:51 | +| presentation/views/settlement_fx_view.gd:73 | probe_banners | probe:3 | +| presentation/views/settlement_fx_view.gd:78 | probe_settlement_marks | probe:1 | +| presentation/views/ap_bar_view.gd:206 | refresh_remaining_text | probe:3 | +| core/refresh_bank.gd:38 | refresh_y | sim:2, test:10 | +| core/config_db.gd:180 | reload | probe:2, test:4 | +| core/config_db.gd:81 | reset_diagnostics | test:3 | +| presentation/views/settings_overlay.gd:26 | retarget | probe:1 | +| presentation/views/debug_overlay.gd:101 | run_data_probe | probe:1 | +| presentation/views/board_view.gd:163 | set_fog_enabled | probe:2 | +| presentation/views/debug_overlay.gd:73 | set_sim_count | probe:1 | +| presentation/views/debug_overlay.gd:58 | set_sliders | probe:4 | +| presentation/views/board_view.gd:345 | set_type_glow_enabled | probe:4, test:5 | +| core/level_runner.gd:144 | start_level | probe:2, sim:2, test:11 | +| presentation/debug_session.gd:31 | start_single | probe:1 | +| presentation/views/board_view.gd:837 | type_glow_probe | probe:13, test:11 | +| presentation/views/battle_end_overlay.gd:204 | verdict_text | probe:2 | + +## 4. 设计契约(🔴 违反即 L3 停机呈报) + +1. 🔴 **游戏行为零变化**——六件全为零调用方死件,删除零运行期可观测面;零 RNG/信号/存档/表值触面。 +2. 🔴 **级联零新增**——删除后全仓不得出现新孤儿函数(§3.2 级联核列逐件留存复核,T2 判据)。 +3. 🔴 **范围纪律**——只删 §3.2 六件;B 段一律不收(注①去向=新立待办);禁顺手清 tests/sim/probe 面(非生产码)。 +4. 🔴 **禁保留无效成员**——删函数禁留空壳/别名/注释存根(code-conventions §1.1 拆分判据⑤)。 +5. 🔴 **断言零削弱**——六件无测试直连(grep 已证),测试文件零触碰、零断言改判。 +6. 🔴 **实现批禁写 `design/` 整个目录与根规则文件**(architecture.md 六件名 grep 零命中,无反哺点——已核)。 +7. 🔴 **MC/模拟零跑**——无数值改动。 8. 🔴 **车道铁律**——实现走独立 worktree。 + +## 5. 范围边界 + +**做**:§3.2 六件删除(含各自专属文档注释行;六件行段见 §7 文件结构图)。 + +**不做**: +- B 段全部 69 件(§3.3;注①去向=新立待办条目,注④归后续裁决); +- 六件级联核保留面(§3.2 末列); +- tests/sim/probe 面的孤儿排查(非生产码,不在登记范围)。 + +**明示不收件的去向**:待办清单新立条目「config_db._validate_skill_pool_links 旧校验件清退与测试改锚」(写卡段同批登记)。 + +## 6. 关联文档同步面 + +| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 | +|---|---|---| +| `design/engineering/architecture/architecture.md` | 六件名 grep 零命中(has_effect_on/bp_read_int/find_token/has_open/set_feedback_state/stop_resolved 全零——已核) | 无同步义务(零反哺点) | +| `game/tests/README.md` | 六件名零命中能力清单(已核);六件所属套件能力行不受影响 | 实现批禁写——批末收尾核对名实一致(零回表预期) | +| 术语表/GDD 各系统文档 | 零关联(纯代码面清退) | 无同步义务 | +| 待办清单 | 本条目状态推进+新立注①条目 | 状态推进归流水线各段,实现批不碰 | + +## 7. 任务卡 + +**卡前文件结构图**(零新增文件;六件全为非行为改动): + +| 文件 | 改动 | 行为改动 | +|---|---|---| +| `game/core/effect_engine.gd` | 删 :203-212(文档注释+函数) | 否(对照表行 1) | +| `game/core/breakpoint_store.gd` | 删 :211-219(注释+函数) | 否(行 2) | +| `game/core/adventure_runner.gd` | 删 :250-252(注释+函数) | 否(行 3) | +| `game/presentation/modal_stack.gd` | 删 :56-62(注释+函数) | 否(行 4) | +| `game/presentation/views/camp_scene_view.gd` | 删 :88-92(函数;无专属注释块) | 否(行 5) | +| `game/core/curio_stop.gd` | 删 :303-305(注释+函数) | 否(行 6) | + +### T0 基点读数(≈0.2 工作日) + +- [ ] 六件定义处行号与所在文件基线键读数固化(`adventure_runner.gd` max-class-variables=23 在册,其余五文件 grep 基线键记录)——落实现报告(删除后只降不升核对面) +- [ ] 六件零调用方证据复核:逐件全仓 grep(.gd+.tscn,词边界)仅定义命中——与 §3.2 一致;不一致即停卡(L3-①) + +### T1 六件删除(≈0.3 工作日) + +- [ ] `effect_engine.gd`:删 :203-212(`## 订阅查询面…` 注释+`has_effect_on` 函数体) +- [ ] `breakpoint_store.gd`:删 :211-219(`# 数值类读侧…` 注释+`_bp_read_int` 函数体) +- [ ] `adventure_runner.gd`:删 :250-252(`# 候选令牌匹配…` 注释+`_find_token` 转发行) +- [ ] `modal_stack.gd`:删 :56-62(`# 开态查询…` 注释+`has_open` 函数体) +- [ ] `camp_scene_view.gd`:删 :88-92(`set_feedback_state` 函数体) +- [ ] `curio_stop.gd`:删 :303-305(`# 放行谓词…` 注释+`stop_resolved` 函数体) +- [ ] 各删除点相邻空行规范化(两空行分隔惯例保持) + +### T2 级联与零残留复查(≈0.15 工作日) + +- [ ] 六件名全仓 grep(.gd+.tscn,词边界)零残留 +- [ ] 级联留存复核(grep 计数落实现报告):`_bp_strategy`/`bp_read`、`curio_deck.find_token`、`has_open_excluding_tags`、`_apply_state` 族、`_resolved`/`_pending`、`_triggers`/`dispatch` 各 ≥1 在役调用方 +- [ ] 新增孤儿扫描:按 §3.1 口径复查六文件——删除不产生新零调用函数 + +### T3 验证闸(≈0.15 工作日) + +- [ ] 孤立验证:`…console.exe --script res://tests/run_all.gd` 单测域全绿(重点 effect_port/breakpoint/curio/modal/camp/adventure 关联套件) +- [ ] 里程碑步出口:`tools/run_all_gates.sh --code` 全绿 + +### T4 产出物落位卡(≈0.1 工作日) + +- [ ] `reports/iter-实现报告-<日期>.md`:各卡状态+实现决定记录+级联留存复核计数+实机目检总清单(登记「无实机目检项」——死件删除零玩家可观测提交面) +- [ ] `reports/iter-反哺点-<日期>.md`:零条(§6 已核;文件仍须落,写明「零反哺点」) + +**估时**:合计中值 ≈0.9 个工作日(区间 0.75–1.25)。 + +## 8. 验证闸(改动—验收对照表) + +| # | 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---|---| +| 1 | has_effect_on 退役(登记原件) | `effect_engine.gd:203-212` 删 | 静态判据:T2 零残留 grep+既有套件全绿(effect_port_test 等当轮跑批执行) | 静态+单元 | `tools/run_all_gates.sh --code`(09 静态扫描+00/01 保险) | 定义行消失+全仓零残留+回归绿三面(死函数无运行期提交面——静态判据+评审注记申报观测面局限) | 既有:效果发射端口单点 | +| 2 | _bp_read_int 退役 | `breakpoint_store.gd:211-219` 删 | 同行 1(断点关联套件为回归锚) | 静态+单元 | 同行 1 | 同行 1 | 既有:断点片段往返与续玩幂等 | +| 3 | _find_token 退役(死包装) | `adventure_runner.gd:250-252` 删 | 同行 1(adventure 关联套件为回归锚) | 静态+单元 | 同行 1 | 同行 1 | 既有:冒险生命周期:开局受理/三关序列/败北线 | +| 4 | has_open 退役 | `modal_stack.gd:56-62` 删 | 同行 1(modal 关联套件为回归锚) | 静态+单元 | 同行 1 | 同行 1 | 既有:模态覆盖层编排与输入锁 | +| 5 | set_feedback_state 退役 | `camp_scene_view.gd:88-92` 删 | 同行 1(camp 关联套件/探针为回归锚) | 静态+单元 | 同行 1 | 同行 1 | 既有:营地场景图十层铺底与门洞留空呈现 | +| 6 | stop_resolved 退役 | `curio_stop.gd:303-305` 删 | 同行 1(curio 关联套件为回归锚) | 静态+单元 | 同行 1 | 同行 1 | 既有:奖励三选一候选/刷新付费/呈现 | +| 7 | 级联零新增(完成定义判据) | 六件删除的伴生核(无新增代码改动) | T2 级联留存复核:§3.2 末列六组保留面 grep 计数各 ≥1 在役调用方+新增孤儿扫描(按 §3.1 口径复查六文件零新孤儿)——计数落实现报告 | 静态 | 同行 1 | 保留面调用计数与新孤儿扫描零命中=本批级联判据特有提交面 | 既有:效果发射端口单点 | + +- **非行为改动行**:六行全部为非行为改动(判断依据:被删函数全仓零调用方〔§3.2 证据〕=无运行期提交面;既有套件全绿兜底)。 +- **测试方式两分法**:本批全部归静态判据+套件回归锚;**无用户目检项**(零玩家可观测提交面)。 +- **测试削弱申报**:无——测试文件零触碰(六件无测试直连,grep 已证)。 +- **独立验收审计**:`reports/iter-验收审计-<日期>.md`,未参与实现的会话或子代理执行,未过不得合并(test-conventions §8.7;验收档中=对照表全量核对+判据绑定面抽查+复跑以远端 CI 现查读数代证)。 +- **回归措辞**:里程碑步(T3)出口跑一次 `--code` 域全套闸;卡内修复只做出红最小单元的最窄确认。 + +## 9. 异常分级 + +- **L1 自救**:空行规范化笔误、grep 白名单笔误。 +- **L2 记录继续**:级联留存复核的计数呈现形态(记录进实现报告)。 +- **L3 停机呈报**:①T0 复核发现任一件实有调用方(字符串派发/动态接线漏判=上游考证缺陷,停卡退回写卡段);②级联复查发现新孤儿(误判级联);③机检键与本卡契约互斥(禁改闸件豁免);④全套闸红不可最小单元收敛。 + +## 10. 代拍板白名单 + +1. 删除点相邻空行规范化。 +2. T2 复核计数落实现报告的表格形态。 + +## 11. 待裁点 + +零待裁点——删除方向 2026-11-04 用户裁;排查口径按登记(生产码总体+测试直连不判死);B 段保留与注①去向新立待办=登记口径的执行。开工门=grilling 复核+用户裁决+迭代编排(编入迭代后估时总量 <5 工作日=冻结待凑批)。 + +## 12. 落位与可复用性预检 + +- **复用面**:零新增件——纯删除。 +- **唯一面**:删除不产生第二落点;三组同名体已核辨——`curio_deck.find_token`(在役真件)与被删 `_find_token`(死包装)是两个标识符域;`has_open` 与 `has_open_excluding_tags`(在役)同理;`stop_resolved` 与信号 `curio_resolved`(在役)同理。 +- **分层与交互面**:零跨层变化——六件均无调用方,删除不改变任何调用路径;Core 零 UI 依赖不因删除新增绕行;零表值触面。 + +## 13. 禁为机检文本保留无效成员 + +🔴 适用(删除侧反向)——禁为保机检键/计数留空壳、别名、注释存根;删除与机检键互斥时实现段一律停卡呈报,不得自造第三条路。 + +## 14. 实现规范预算 + +零新增文件——三查不适用。存量触面六文件:函数删除不改类变量数(`adventure_runner.gd` max-class-variables=23 在册,读数不变);gdstyle 函数级键(若有)只降不升;S13 公开方法数只降不升(六件中 `has_effect_on`/`has_open`/`set_feedback_state`/`stop_resolved` 为公开方法——四文件 S13 计数各 −1,T0 读数承载)。全部触面 S13 键较登记值只降不升。 + +## 15. 拆分边界纪律 + +本卡为删除类(非拆分/收敛类),无拆出单元。红线:禁为凑指标或摊薄审查把六件拆多批留尾——六件一次清完(T1 六个复选框是施工粒度,非范围拆分);禁把 B 段任何件搭车收进本批(§4 契约 3)。 diff --git a/待办清单.md b/待办清单.md index 6562af40..3b4f09f2 100644 --- a/待办清单.md +++ b/待办清单.md @@ -257,15 +257,23 @@ #### minesweeper_rules 公开面还债——S13 超标键撤销后的清偿(2026-11-04 登记,来源=批次移交,优先级=P0) -**实况**:证据=tools/gdstyle_baseline.txt(本批撤销无凭证键后,gdstyle 闸红单键:game/core/minesweeper_rules.gd 公开方法 21>上限 20);+2 搬家方法 harmful_variants/harmful_adjacent 为迭代51卡② R2「五方法归位」裁决的直接后果(git 提交 7f512aa8),基点漂移考证:写卡基点 6261b36e 实数 18,iter50-卡3 a2886a53 +1 → 实现基点 19,+2=21。还债方向:settle_batch/fx_reveal 族收敛或搬迁,公开方法 21→≤20;还债批合回后 gdstyle 归绿(CI 追绿),期间代码域 CI 红在案、文档批不受影响(域分流)。 +**实况**:证据=tools/gdstyle_baseline.txt(本批撤销无凭证键后,gdstyle 闸红单键:game/core/minesweeper_rules.gd 公开方法 21>上限 20);+2 搬家方法 harmful_variants/harmful_adjacent 为迭代51卡② R2「五方法归位」裁决的直接后果(git 提交 7f512aa8),基点漂移考证:写卡基点 6261b36e 实数 18,iter50-卡3 a2886a53 +1 → 实现基点 19,+2=21。还债方向经 21 公开方法全量盘点收窄=safe_remaining 私有化(其余 20 个有生产调用方;preview_reveal=架构在册 sim 出口;settle_batch/fx_reveal 受架构在册边界与职责轴保护)——需求卡片 reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md(实现批输入合同,0.5 工作日)。 **改动面**:游戏代码 -**状态**:待写卡 +**状态**:已写卡 #### EffectEngine 死函数与全仓孤儿函数清退(2026-11-04 登记,来源=会话提及,优先级=P0) -**实况**:证据=game/core/effect_engine.gd:204 has_effect_on 全仓零调用方(迭代51卡①删唯一调用方后成死码,grep 仅定义命中;卡① 备案见 git 提交 7f512aa8;2026-11-04 用户裁=删并全仓排查)。范围=删除 has_effect_on+全仓排查生产码零调用死函数/孤儿函数一并清退;测试直连与受控例外面不算死码,排查随写卡段考证。 +**实况**:证据=game/core/effect_engine.gd:204 has_effect_on 全仓零调用方(迭代51卡①删唯一调用方后成死码,grep 仅定义命中;卡① 备案见 git 提交 7f512aa8;2026-11-04 用户裁=删并全仓排查)。排查已于写卡段完成:判死六件(has_effect_on/bp_read_int/find_token/has_open/set_feedback_state/stop_resolved,级联零牵连),B 段 69 件生产零调用仅测量面按登记口径保留——需求卡片 reports/需求卡片-孤儿函数清退批-2026-11-04.md(实现批输入合同,0.9 工作日)。 + +**改动面**:游戏代码 + +**状态**:已写卡 + +#### config_db._validate_skill_pool_links 旧校验件清退与测试改锚(2026-11-04 登记,来源=审计发现) + +**实况**:config_db.gd:170 _validate_skill_pool_links 生产零调用——技能池链接校验生产真身已迁 config_db_cross_validator.gd:197-213(同判定类别 dangling_skill/pool_composition,检查面更全);旧件测试直连 5 处(round_flow_skill_pool_test.gd:869/871/876/893/905),测试锚在被取代实现。处置方向待裁(删旧件+测试改锚跨表校验件〔推荐〕vs 留旧件作测试对照件)。证据=孤儿函数清退批写卡段全仓排查(reports/需求卡片-孤儿函数清退批-2026-11-04.md §3.3 注①)。 **改动面**:游戏代码 From 90eac889ea71aecbc20cfc991ed48bd9a5d9f508 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 14:54:02 +0800 Subject: [PATCH 19/19] =?UTF-8?q?=E7=BC=96=E6=8E=92(=E8=BF=AD=E4=BB=A352?= =?UTF-8?q?=E5=87=91=E6=89=B9):=20=E4=B8=A4P0=E5=8D=A1=E7=BC=96=E5=85=A5?= =?UTF-8?q?=E2=80=94=E2=80=94minesweeper=5Frules=E5=85=AC=E5=BC=80?= =?UTF-8?q?=E9=9D=A2=E8=BF=98=E5=80=BA0.5(=E5=85=88=E8=A1=8C=EF=BC=8Cgdsty?= =?UTF-8?q?le=20S13=E6=B4=BB=E7=BA=A2=E9=9A=8F=E6=89=B9=E8=BF=BD=E7=BB=BF)?= =?UTF-8?q?/=E5=AD=A4=E5=84=BF=E5=87=BD=E6=95=B0=E6=B8=85=E9=80=800.9?= =?UTF-8?q?=EF=BC=8C=E5=8E=9F=E4=B8=A4=E5=8D=A1=E9=A1=BA=E7=A7=BB=E2=91=A2?= =?UTF-8?q?=E2=91=A3(presentation4.125=E2=86=92=E5=B0=81=E5=8D=B0=E5=8D=B0?= =?UTF-8?q?=E8=AE=B03.75=EF=BC=8C=E5=B7=B2=E5=AE=9A=E7=9B=B8=E9=82=BB?= =?UTF-8?q?=E5=BA=8F=E4=B8=8D=E5=8A=A8)=EF=BC=8C=E5=90=88=E8=AE=A1?= =?UTF-8?q?=E2=89=889.275=E5=B7=A5=E4=BD=9C=E6=97=A5(5-10=E5=B8=B8?= =?UTF-8?q?=E8=A7=84=E5=8C=BA=E9=97=B4=E5=85=8D=E7=89=B9=E6=89=B9=E3=80=81?= =?UTF-8?q?=E8=A1=8C=E7=8A=B6=E6=80=81=E7=85=A7=E6=97=A7=E5=BE=85=E5=BC=80?= =?UTF-8?q?=E5=B7=A5)=EF=BC=9B=E5=BE=85=E5=8A=9E=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E4=B8=A4=E6=9D=A1=E7=9B=AE=E7=8A=B6=E6=80=81=E6=8E=A8=E8=BF=9B?= =?UTF-8?q?=EF=BC=8B=E5=B7=B2=E7=BC=96=E8=BF=AD=E4=BB=A352=EF=BC=9Broadmap?= =?UTF-8?q?=E8=BF=AD=E4=BB=A352=E8=A1=8C=E5=8D=A1=E7=89=87=E5=88=97?= =?UTF-8?q?=E5=9B=9B=E5=8D=A1=E9=87=8D=E6=8E=92=EF=BC=8B=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E4=BD=8D=E4=B8=8B=E4=B8=80=E6=AD=A5=E6=8D=A2=E4=BB=A3(P0?= =?UTF-8?q?=E5=BE=85=E5=86=99=E5=8D=A1=E5=8F=A5=E9=80=80=E5=BD=B9=E3=80=81?= =?UTF-8?q?=E5=BE=85=E7=BC=96=E6=B1=A0=E8=A1=A5config=5Fdb=E6=97=A7?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BB=B6)=E3=80=81CI=E8=AF=BB=E6=95=B0?= =?UTF-8?q?=E5=88=B7=E6=96=B0run=2062=E7=BB=BF(131c63e7)=EF=BC=9B=E4=BB=B6?= =?UTF-8?q?=E6=A0=B8=EF=BC=9A7.875=E5=8F=AA=E4=BD=99=E8=BF=AD=E4=BB=A344?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=A1=8C=E3=80=81=E5=9B=9B=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E3=80=8C=E5=B7=B2=E7=BC=96=E8=BF=AD=E4=BB=A352=E3=80=8D?= =?UTF-8?q?=E4=B8=8E=E5=8D=A1=E7=89=87=E5=88=97=E5=9B=9B=E5=8D=A1=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=EF=BC=9Bpipeline=5Fgate=20PASS(28=E6=9D=A1=E7=9B=AE/5?= =?UTF-8?q?2=E6=80=BB=E8=A1=A8=E8=A1=8C/=E4=BA=A4=E5=8F=89=E6=A0=B84?= =?UTF-8?q?=E9=93=BE/=E5=8D=A1=E7=89=879=E5=BC=A0)=EF=BC=9Brun=5Fall=5Fgat?= =?UTF-8?q?es=20--docs=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87=E9=9B=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roadmap.md | 6 +++--- 待办清单.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/roadmap.md b/roadmap.md index b8f4d69e..f5da689f 100644 --- a/roadmap.md +++ b/roadmap.md @@ -5,8 +5,8 @@ ## 当前位(只回答「现在该做什么」;三行内随批次换代,禁止在此扩写成状态快照) - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 -- **迭代位**:迭代 1–51 全部已完成——迭代 51(四卡:EffectPort 蓄力层删键/架构审查 5.1 封装修复/P2 NODE_TYPES 词表单点化/L1 深度 3 混编)2026-11-04 合回(合并提交 fb27dc2f,独立验收审计终判通过),批后收尾闭合(2026-11-04):反哺点十条落文 architecture.md(第十一条落点不存在跳过、呈报在案)+S13 无凭证基线键经用户裁撤销(--refresh 重生成入口同批删除、基线改人工修订,gdstyle 红在案由还债 P0 追绿)+还债与死函数清退两条 P0 登记+presentation 卡未编迭代迁出承接条目+产出物退役三件+待办三条目删除。迭代 50 收尾记录见 git 提交 47c752ea,迭代 49 见 aa396eb9。远端 CI 现查:master run 59 绿(commit 3c264160,跑闸步真跑过)。 -- **下一步**:迭代 52 已编入待开工(两卡 ≈7.875 个工作日,`/iter 52` 发令)。P0 两条待写卡(minesweeper_rules 公开面还债——gdstyle 红在案追绿;EffectEngine 死函数与全仓孤儿函数清退)。待编池:改写族孤儿(待处置方向裁决)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 +- **迭代位**:迭代 1–51 全部已完成——迭代 51(四卡:EffectPort 蓄力层删键/架构审查 5.1 封装修复/P2 NODE_TYPES 词表单点化/L1 深度 3 混编)2026-11-04 合回(合并提交 fb27dc2f,独立验收审计终判通过),批后收尾闭合(2026-11-04):反哺点十条落文 architecture.md(第十一条落点不存在跳过、呈报在案)+S13 无凭证基线键经用户裁撤销(--refresh 重生成入口同批删除、基线改人工修订,gdstyle 红在案由还债 P0 追绿)+还债与死函数清退两条 P0 登记+presentation 卡未编迭代迁出承接条目+产出物退役三件+待办三条目删除。迭代 50 收尾记录见 git 提交 47c752ea,迭代 49 见 aa396eb9。远端 CI 现查:master run 62 绿(commit 131c63e7,跑闸步真跑过)。 +- **下一步**:迭代 52 已编入待开工(四卡 ≈9.275 个工作日——P0 还债+P0 孤儿清退+presentation 封装修复+封印印记法阵化,`/iter 52` 发令;gdstyle S13 活红归还债卡落批追绿)。待编池:config_db 旧校验件(待写卡、处置方向待裁)、改写族孤儿(待处置方向裁决)、二三关棋盘观感(待设计+待美术)、EC8 热重载实装(待用户另立条目)。 ## 里程碑时间线 @@ -85,7 +85,7 @@ | 迭代 49 | 已完成(2026-11-02) | 已退役(git 历史可考) | 2026-11-02 | ≈7.25 个工作日 | 已退役(git 历史可考) | | 迭代 50 | 已完成(2026-11-03) | 已退役(git 历史可考) | 2026-11-03 | ≈12.875 个工作日〔用户特批 2026-11-03 超 10 个工作日〕 | 已退役(git 历史可考) | | 迭代 51 | 已完成(2026-11-04) | 已退役(git 历史可考) | 2026-11-04 | ≈6.625 个工作日 | 已退役(git 历史可考) | -| 迭代 52 | 待开工 | reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md
reports/需求卡片-封印印记法阵化批-2026-11-04.md | 2026-11-04 | ≈7.875 个工作日 | — | +| 迭代 52 | 待开工 | reports/需求卡片-minesweeper_rules公开面还债批-2026-11-04.md
reports/需求卡片-孤儿函数清退批-2026-11-04.md
reports/需求卡片-架构审查presentation层封装修复批-2026-11-04.md
reports/需求卡片-封印印记法阵化批-2026-11-04.md | 2026-11-04 | ≈9.275 个工作日 | — | - 迭代 38 收口为带红合并〔用户特批 2026-10-26〕:09 静态扫描 S13 gdstyle 恶化 2 条经批准记入基线存量红(git 提交 c4c69701),卡② 停卡退上游,处方已于 2026-10-26 修订定稿 v1.3(git 提交 b3ea7532),随「gdstyle 存量债全量清偿」条目波 7 待编排。 - 迭代 39 收口为带红合并〔用户特批 2026-10-27〕:02 EV 锚 goblin 0.9148∉[1.00,1.20](触发器批有意行为改动,fp f74b9a48ef4a)已随 EV 锚重锚批换带归绿(git 提交 ba5105aa);08d 生涯.md:55 行号漂移已随 2026-10-28 批后收尾反哺落文修复归绿。三卡拆分后终局 gdstyle 恶化 2 条(battle_orchestrator max-file-length、level_map_screen max-class-variables)归迭代 40 卡①还债与迭代 41 存量债序列。 diff --git a/待办清单.md b/待办清单.md index a90fad21..2e49e749 100644 --- a/待办清单.md +++ b/待办清单.md @@ -261,7 +261,7 @@ **改动面**:游戏代码 -**状态**:已写卡 +**状态**:已写卡+已编迭代 52 #### EffectEngine 死函数与全仓孤儿函数清退(2026-11-04 登记,来源=会话提及,优先级=P0) @@ -269,7 +269,7 @@ **改动面**:游戏代码 -**状态**:已写卡 +**状态**:已写卡+已编迭代 52 #### config_db._validate_skill_pool_links 旧校验件清退与测试改锚(2026-11-04 登记,来源=审计发现)