From cc2be27b9d3f0e335e237ad7b51e389688f1c953 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 05:03:39 +0800 Subject: [PATCH 01/19] =?UTF-8?q?=E7=99=BB=E8=AE=B0(=E8=90=A5=E5=9C=B0?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E4=BF=AE=E5=A4=8D=E6=89=B9=C2=B7=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=A3=81=E4=B8=80=E6=9D=A1=E6=9D=A1=E7=9B=AE):=20?= =?UTF-8?q?=E4=B8=89=E4=BB=B6=E5=90=8C=E6=89=B9=E4=B8=8D=E5=90=88=E5=8D=A1?= =?UTF-8?q?=E6=94=B6=E4=B8=80=E6=9D=A1=E5=BE=85=E5=8A=9E=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=A4=A7=E9=97=A8=E6=97=A0=E6=96=AD=E7=82=B9=E5=BC=80=E5=B1=80?= =?UTF-8?q?=E5=9B=9E=E4=B8=BB=E8=8F=9C=E5=8D=95=EF=BC=8F=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=A1=86=E5=90=A6=E5=B7=A6=E6=98=AF=E5=8F=B3=EF=BC=8F=E8=90=A5?= =?UTF-8?q?=E5=9C=B0=E9=A1=B6=E6=A0=8F=E6=97=A0=E6=A0=8F=E4=BD=93;=20?= =?UTF-8?q?=E5=AE=9E=E5=86=B5=E9=80=90=E4=BB=B6=E6=8C=87=E8=AE=A4=20bug=20?= =?UTF-8?q?=E8=AF=81=E6=8D=AE=E8=A1=8C=E9=94=9A(camp=5Fshop=5Fpresenter.gd?= =?UTF-8?q?:85=20=E6=97=A0=E6=9D=A1=E4=BB=B6=E6=98=BE=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E5=B1=82=EF=BC=8Bmain.gd:211-212/223-224=20=E5=A4=A7=E9=97=A8?= =?UTF-8?q?=E4=B8=A4=E5=87=BA=E5=8F=A3=E5=85=88=E5=85=B3=E8=90=A5=E5=9C=B0?= =?UTF-8?q?=E5=86=8D=E5=BC=80=E5=B1=80=EF=BC=8Bmain.gd:292-311=20=5Fstart?= =?UTF-8?q?=5Fadventure=20=E6=97=A0=E9=9A=90=E8=97=8F=E5=8F=A5,=E8=AF=A5?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=B5=8B=E8=AF=95=E4=B8=8E=E6=8E=A2=E9=92=88?= =?UTF-8?q?=E9=9B=B6=E8=A6=86=E7=9B=96;=20confirm=5Fdialog.gd:31-39=20add?= =?UTF-8?q?=5Fchild=20=E5=BA=8F=3D=E5=8F=96=E6=B6=88=E2=86=92=E6=AC=A1?= =?UTF-8?q?=E2=86=92=E7=A1=AE=E8=AE=A4;=20camp=5Fhud=5Fband.gd:10-17=20?= =?UTF-8?q?=E9=80=8F=E6=98=8E=20Control=20=E6=97=A0=E6=A0=8F=E4=BD=93)?= =?UTF-8?q?=E4=B8=8E=20camp.md=20v2.7=20=C2=A7=E9=A1=B6=E6=A0=8F=E5=8F=AA?= =?UTF-8?q?=E5=AE=9A=E9=AB=9848/=E5=85=A8=E5=AE=BD/=E6=B5=AE=E5=B1=82?= =?UTF-8?q?=E6=9C=AA=E5=AE=9A=E5=BA=95=E8=89=B2=C2=B7=E6=8E=92=E5=B8=83?= =?UTF-8?q?=C2=B7=E5=BE=BD=E7=AB=A0=E8=90=BD=E4=BD=8D;=20=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3=E6=AE=B5=E7=95=99=E4=B8=A4=E9=A1=B9(=E9=A1=B6?= =?UTF-8?q?=E6=A0=8F=E8=A1=A5=E8=AE=BE=E8=AE=A1=EF=BC=8F=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=A1=86=E7=9B=AE=E6=A0=87=E9=92=AE=E5=BA=8F=E5=BE=85=E8=A3=81?= =?UTF-8?q?)=E2=80=94=E2=80=94=E7=AC=AC2=E6=9D=A1=E6=8C=89=20art-bible=20?= =?UTF-8?q?=C2=A74.5=20#7=E3=80=8C=E7=A0=B4=E5=9D=8F=E6=80=A7=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E7=A6=81=E6=AD=A2=E4=B8=8E=E5=B8=B8=E8=A7=84=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E5=90=8C=E4=BD=8D=E3=80=8D=E6=A0=B8=E5=AE=9E=E4=B8=8E?= =?UTF-8?q?=E6=9C=AC=E6=89=B9=E9=92=AE=E5=BA=8F=E6=94=B9=E5=8A=A8=E6=97=A0?= =?UTF-8?q?=E5=85=B3(=E5=85=B1=E7=94=A8=E4=BB=B6=E6=8C=89=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E8=B7=AF=E7=94=B1=E4=B8=8D=E6=8C=89=E4=BD=8D=E6=AC=A1?= =?UTF-8?q?,=E5=90=8C=E4=BD=8D=E7=B3=BB=E6=97=A2=E6=9C=89=E7=8A=B6?= =?UTF-8?q?=E6=80=81,=E4=B8=8D=E9=9A=8F=E6=9C=AC=E6=89=B9=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E4=B9=9F=E4=B8=8D=E7=94=B1=E6=9C=AC=E6=89=B9=E4=BF=AE?= =?UTF-8?q?),=E6=95=85=E6=9C=AA=E6=8A=8A=20=C2=A74.5=20#7=20=E6=8B=89?= =?UTF-8?q?=E8=BF=9B=E6=9C=AC=E6=9D=A1=E6=94=B9=E5=8A=A8=E9=9D=A2;=20?= =?UTF-8?q?=E6=94=B9=E5=8A=A8=E9=9D=A2=3D=E6=96=87=E6=A1=A3+=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E4=BB=A3=E7=A0=81;=20=E7=8A=B6=E6=80=81=3D=E5=BE=85?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=AE=BE=E8=AE=A1(=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E6=80=A7=E6=9D=A1=E7=9B=AE=E5=90=AB=E6=9C=AA=E9=97=AD=E5=90=88?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=AE=B5,=E7=A6=81=E4=B8=8E=E5=BE=85?= =?UTF-8?q?=E5=86=99=E5=8D=A1=E5=B9=B6=E7=8E=B0)=20=E6=9C=AC=E6=AD=A5?= =?UTF-8?q?=E5=87=AD=E6=8D=AE:=20=E6=B5=81=E6=B0=B4=E7=BA=BF=E9=97=B8?= =?UTF-8?q?=E7=9B=B4=E8=B7=91=20PASS(=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=2025=20=E6=9D=A1/=E6=AD=A3=E6=96=87=E9=99=90=E9=95=BF=E8=B6=85?= =?UTF-8?q?=E9=99=90=200)+tools/run=5Fall=5Fgates.sh=20--docs=20exit=3D0?= =?UTF-8?q?=20=E9=97=B8=E5=86=B5=E4=B8=8E=E5=9F=BA=E7=BA=BF=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=97=A0=E6=96=B0=E5=A2=9E=E7=BA=A2(=E5=9B=9E?= =?UTF-8?q?=E6=89=A7=20game/tests/.gates/receipt.json,=E5=9F=BA=E7=BA=BF?= =?UTF-8?q?=20commit=2069c417d8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 待办清单.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/待办清单.md b/待办清单.md index df41ec99..fe0f38d3 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,6 +2,20 @@ 本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 +#### 营地交互修复批——大门无断点开局回主菜单/确认框钮序/营地顶栏形态(2026-11-05 登记,来源=会话提及) + +**实况**:三件同批解决、互不依赖素材,后续拆卡不合卡。 +- bug 证据①无断点开局回主菜单:`game/presentation/camp_shop_presenter.gd:85`(`on_camp_closed()` 无条件显开始层)→ `game/presentation/main.gd:211-212`/`223-224`(大门两出口先关营地再开局)→ `main.gd:292-311`(`_start_adventure()` 无隐藏语句);该路径测试与探针零覆盖(启动全走 `_start_adventure_from_menu()`) +- bug 证据②确认框否左是右:`game/presentation/views/confirm_dialog.gd:31-39`(`_init` 内 add_child 序=取消→次→确认;信号按按钮对象路由不按位次,换文案不改位次) +- bug 证据③营地顶栏无栏体:`game/presentation/views/camp_hud_band.gd:10-17`(透明 Control 只挂右上钥石徽章) +- 相关文档 [营地屏 UX spec](design/gdd/ux/camp.md) v2.7 §顶栏(只定高 48px/全宽/浮层覆盖,未定底色·内容排布·徽章落位) + +**缺口**:camp.md §顶栏 段待补(底色/内容排布/徽章落位);确认框目标钮序待裁(确认左/确认右)。 + +**改动面**:文档+游戏代码 + +**状态**:待文档设计 + #### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(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/` 齐备,可开跑。 From 00d7bbfba188f2d8bdb589b7469554126a907307 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 07:07:46 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=AE=BE=E8=AE=A1(?= =?UTF-8?q?=E8=90=A5=E5=9C=B0=E4=BA=A4=E4=BA=92=E4=BF=AE=E5=A4=8D=E6=89=B9?= =?UTF-8?q?=C2=B7=E5=AE=9A=E7=A8=BF=E8=90=BD=E6=96=87=E5=9B=9B=E5=A4=84):?= =?UTF-8?q?=20=E5=BE=85=E8=A3=81=E7=82=B9=E6=B8=85=E9=9B=B6=E5=90=8E?= =?UTF-8?q?=E5=86=99=E5=85=A5=E2=80=94=E2=80=94=E2=91=A0camp.md=20v2.8=20?= =?UTF-8?q?=C2=A7=E9=A1=B6=E6=A0=8F=20=E8=A1=A5=E6=A0=8F=E4=BD=93=E5=AE=9A?= =?UTF-8?q?=E5=80=BC(=E5=BA=95=3DCAMP=5FHEADER=5FBAND=20#6E4526=C3=97?= =?UTF-8?q?=E4=B8=8D=E9=80=8F=E6=98=8E=E5=BA=A60.80=E3=80=81=E9=AB=9848?= =?UTF-8?q?=E5=85=A8=E5=AE=BD=E6=B5=AE=E5=B1=82=E3=80=81=E6=A0=8F=E5=86=85?= =?UTF-8?q?=E5=8F=AA=E9=92=A5=E7=9F=B3=E5=BE=BD=E7=AB=A0=E4=B8=80=E4=BB=B6?= =?UTF-8?q?=E5=8F=B3=E4=BD=8D=E9=A1=B6=E6=A0=BC=E3=80=94=E5=BE=BD=E7=AB=A0?= =?UTF-8?q?=E8=87=AA=E9=AB=9848=EF=BC=9D=E6=A0=8F=E9=AB=98=E6=95=85?= =?UTF-8?q?=E6=97=A0=E9=A1=B6=E8=B7=9D,=E7=B3=BB=E5=B0=BA=E5=AF=B8?= =?UTF-8?q?=E6=8E=A8=E5=AF=BC=E9=9D=9E=E9=80=89=E6=8B=A9=E3=80=95=E3=80=81?= =?UTF-8?q?=E5=B7=A6=E4=BD=8D=E4=B8=8D=E6=94=BE=E4=BB=B6,=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=3D=E7=9B=AE=E6=A3=80=E7=AA=84=E5=B8=A6=E5=8F=AF?= =?UTF-8?q?=E8=BE=A8=EF=BC=8B=E6=9C=BA=E6=A3=80=E6=A0=8F=E4=BD=93=E5=91=88?= =?UTF-8?q?=E8=89=B2=E5=AF=B9=E8=90=BD=E4=BD=8D=E5=B8=A6=E5=BA=95=E8=89=B2?= =?UTF-8?q?=E2=89=A53:1)=20=E2=91=A1art-bible=20v3.43=20=C2=A76.2=20?= =?UTF-8?q?=E9=92=A5=E7=9F=B3=E4=BD=99=E9=A2=9D=E8=A1=8C=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=90=8C=E7=BB=84=E5=AE=9A=E5=80=BC=E5=B9=B6=E6=8C=87=E5=9B=9E?= =?UTF-8?q?=20camp=20=C2=A7=E9=A1=B6=E6=A0=8F=20=E4=B8=BA=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E4=BA=8B=E5=AE=9E=E6=BA=90(=E9=98=B2=E4=B8=A4?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=90=84=E8=AF=B4=E4=B8=80=E5=A5=97)=20?= =?UTF-8?q?=E2=91=A2HUD=E6=A1=86=E6=9E=B6=20=C2=A7=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=B8=8E=E5=BC=B9=E5=B1=82=20=E7=94=B1=E3=80=8C=E4=B8=A4?= =?UTF-8?q?=E6=9D=A1=E7=BA=AA=E5=BE=8B=E3=80=8D=E6=94=B9=E3=80=8C=E4=B8=89?= =?UTF-8?q?=E6=9D=A1=E7=BA=AA=E5=BE=8B=E3=80=8D=E6=96=B0=E5=A2=9E=E5=85=B6?= =?UTF-8?q?=E4=B8=89=3D=E7=A1=AE=E8=AE=A4=E5=BC=B9=E7=AA=97=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E4=BD=8D=E6=AC=A1=E5=AE=9A=E5=80=BC(=E6=8E=A8?= =?UTF-8?q?=E8=BF=9B=E9=92=AE=E5=9C=A8=E5=B7=A6=E3=80=81=E7=BB=B4=E6=8C=81?= =?UTF-8?q?=E7=8E=B0=E7=8A=B6=E9=92=AE=E3=80=94=E5=8F=96=E6=B6=88/?= =?UTF-8?q?=E7=95=99=E4=B8=8B/=E5=90=A6=E3=80=95=E5=9C=A8=E5=8F=B3?= =?UTF-8?q?=E3=80=81=E4=B8=89=E9=92=AE=E5=BD=A2=E6=80=81=E6=AC=A1=E6=8E=A8?= =?UTF-8?q?=E8=BF=9B=E5=B1=85=E4=B8=AD,=E5=9B=9B=E5=A4=84=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E6=96=B9=E5=85=B1=E7=94=A8=E5=90=8C=E4=B8=80=E4=BB=B6?= =?UTF-8?q?=E5=90=8C=E4=B8=80=E5=BA=8F,=E4=BD=8D=E6=AC=A1=E4=B8=8D?= =?UTF-8?q?=E9=9A=8F=E6=96=87=E6=A1=88=E5=8F=98=E8=AF=AD=E4=B9=89=E7=94=B1?= =?UTF-8?q?=E6=96=87=E6=A1=88=E6=89=BF=E6=8B=85),=E5=87=BA=E5=A4=84?= =?UTF-8?q?=E6=8C=89=E7=94=A8=E6=88=B7=E8=A3=81=E8=90=BD=20HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=20=E8=80=8C=E9=9D=9E=20interaction-patterns=20?= =?UTF-8?q?=E5=BA=93(=E8=AF=A5=E5=BA=93=E6=96=87=E4=BB=B6=E5=A4=B4?= =?UTF-8?q?=E8=87=AA=E5=A3=B0=E6=98=8E=E3=80=8C=E4=B8=8D=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E6=96=B0=E8=AE=BE=E8=AE=A1=E5=86=B3=E7=AD=96=E3=80=8D=E4=B8=94?= =?UTF-8?q?=E5=85=B6=E6=88=90=E6=96=87=E6=9D=A5=E5=8E=86=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=AD=98=E7=96=91)=20=E2=91=A3=E5=BE=85=E5=8A=9E=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E6=9C=AC=E6=9D=A1=E7=8A=B6=E6=80=81=20=E5=BE=85?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=AE=BE=E8=AE=A1=E2=86=92=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=B7=B2=E9=97=AD=E5=90=88=EF=BC=8B=E5=BE=85?= =?UTF-8?q?=E5=86=99=E5=8D=A1,=E5=88=A0=E3=80=8C=E7=BC=BA=E5=8F=A3?= =?UTF-8?q?=E3=80=8D=E5=B1=9E=E6=80=A7(=C2=A712.1:=E5=B7=B2=E9=97=AD?= =?UTF-8?q?=E5=90=88=E6=80=81=E4=B8=8D=E5=BE=97=E6=AE=8B=E7=95=99),?= =?UTF-8?q?=E5=AE=9E=E5=86=B5=E6=94=B9=E5=86=99=E4=B8=BA=E4=B8=89=E4=BB=B6?= =?UTF-8?q?=E4=B8=80=E5=BC=A0=E5=8D=A1=EF=BC=8B=E4=B8=89=E4=BB=BD=E5=AE=9A?= =?UTF-8?q?=E7=A8=BF=E6=8C=87=E9=92=88=E5=B8=A6=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?,=E6=9D=A1=E7=9B=AE=E5=90=8D=E3=80=8C=E5=BC=80=E5=B1=80?= =?UTF-8?q?=E5=9B=9E=E4=B8=BB=E8=8F=9C=E5=8D=95=E3=80=8D=E8=AE=A2=E6=AD=A3?= =?UTF-8?q?=E4=B8=BA=E3=80=8C=E5=BC=80=E5=B1=80=E8=A2=AB=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E5=B1=82=E7=9B=96=E4=BD=8F=E3=80=8D(=E7=9C=9F=E6=9C=BA?= =?UTF-8?q?=E7=8E=B0=E8=B1=A1=E7=B3=BB=E5=86=92=E9=99=A9=E5=B7=B2=E5=9C=A8?= =?UTF-8?q?=E5=BA=95=E4=B8=8B=E5=BC=80=E5=B1=80=E3=80=81=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E5=B1=82=E7=9B=96=E4=BD=8F=E2=80=94=E2=80=94system=5Ftest.gd:8?= =?UTF-8?q?7-89=20=E5=B1=82=E5=BA=8F=E6=96=AD=E8=A8=80=E4=B8=BA=E6=8D=AE);?= =?UTF-8?q?=20=E8=AE=BE=E8=AE=A1=E4=BE=9D=E6=8D=AE=3D=E6=9C=AC=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=94=A8=E6=88=B7=E8=A3=81(=CE=B10.80=20=E5=8D=8A?= =?UTF-8?q?=E9=80=8F=E6=98=8E=E6=A1=A3=E4=B8=BA=E5=AE=9A=E5=80=BC/?= =?UTF-8?q?=E6=A0=8F=E5=86=85=E5=8D=95=E4=BB=B6/=E9=92=AE=E5=BA=8F?= =?UTF-8?q?=E5=B7=A6=E6=8E=A8=E8=BF=9B=E5=8F=B3=E7=BB=B4=E6=8C=81)+?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E4=BA=BA=E5=AE=A1=E3=80=8C=E6=88=91=E7=9C=8B?= =?UTF-8?q?=E4=BA=86=E9=A2=84=E8=A7=88=E7=9A=84=E6=95=88=E6=9E=9C=E6=98=AF?= =?UTF-8?q?OK=E7=9A=84=E3=80=8D;=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:?= =?UTF-8?q?=20pipeline=5Fgate=20=E7=9B=B4=E8=B7=91=20PASS(=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE25/=E5=8D=A1=E7=89=87=E5=8F=A3=E5=BE=841=E5=BC=A0?= =?UTF-8?q?=E5=85=A8=E8=BF=87)+tools/run=5Fall=5Fgates.sh=20--docs=20exit?= =?UTF-8?q?=3D0=20=E9=97=B8=E5=86=B5=E4=B8=8E=E5=9F=BA=E7=BA=BF=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=97=A0=E6=96=B0=E5=A2=9E=E7=BA=A2(=E5=9B=9E?= =?UTF-8?q?=E6=89=A7=20domain=3Ddocs,=E5=9F=BA=E7=BA=BF=20cc2be27b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/art/art-bible.md | 4 ++-- design/gdd/systems/表现与支撑/HUD框架.md | 2 +- design/gdd/ux/camp.md | 4 ++-- 待办清单.md | 15 ++++++--------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 1ca77c1f..7b973957 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.42,历次修订明细见 git 提交史 +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.43,历次修订明细见 git 提交史 > > **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。 > 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。 @@ -395,7 +395,7 @@ - **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB);**实装落盘矩形 `(0, 261, 1280, 49)`、底锚 y310**(Y 压缩按桩身 43px 终稿锁定值精确,落带内〔规划带 (0,240,1280,70)〕;备选 A′〔带高 70/桩身 62〕存档 artwork 同目录)。 - **色彩与氛围依据**:营地场景的冷暖分工(冷的大面积后退、暖的小面积前进)、空气透视三档与大面积用色规则 = `reports/提案-营地色彩与氛围脚本-2026-10-23.md`。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。 - **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。 -- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。 +- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。栏体底=`CAMP_HEADER_BAND` `#6E4526`(§7.2 五 token 对齐表在册色)× 不透明度 0.80;栏内只钥石徽章一件、右位顶格,左位不放件。呈现定值的唯一事实源=[camp](../gdd/ux/camp.md) §顶栏,本节不复述判据。验收:目检〈顶栏成一条可辨窄带,徽章落在带内〉 - **事实源边界**:解锁条件/价格/存档写入归元进度 GDD 与 `MetaProgression`;素材命名与路径归 `asset-path-map`;清单登记归 `art-assets-checklist` §3.5。 - **已裁(2026-11-01 用户裁)**:① **营地环境子板色值**——不前置拍板:量化场景实际用色后按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——门扇 240×220 追认现值,坐标按门洞 x520–760 对齐(`camp-scene-prompts.md` §2.3);③ **帐篷的落地尺寸**——300×170 追认现值(各素材第一张样本稿已过审为终稿,重定即触发重出,不裁)。 diff --git a/design/gdd/systems/表现与支撑/HUD框架.md b/design/gdd/systems/表现与支撑/HUD框架.md index 3ee13988..50deaeb7 100644 --- a/design/gdd/systems/表现与支撑/HUD框架.md +++ b/design/gdd/systems/表现与支撑/HUD框架.md @@ -87,7 +87,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | toast | 非模态 | 自动消退 2.5 秒,不打断挖掘 | | 图例面板 / 明细回看 / 胜负终局 | 模态 | 同一张登记序栈:统一入栈出栈、后开置顶、关闭逐层恢复 | -两条纪律横跨两族。其一,"第一次遇到"类提示(本作没有双击快开、挖不相邻格要先走近)走 toast,每场冒险至多弹一次。其二,拒绝反馈不属于一次性族——每次被拒都要可感知,且图标与文案双通道冗余(不靠颜色单独承载);棋盘面拒绝(动作不合棋盘规则,目标格边框闪)与资源面拒绝(行动点或储备不足,数字脉冲)必须可区分。图例面板恒可随时打开回看,不设解锁门。 +三条纪律横跨两族。其一,"第一次遇到"类提示(本作没有双击快开、挖不相邻格要先走近)走 toast,每场冒险至多弹一次。其二,拒绝反馈不属于一次性族——每次被拒都要可感知,且图标与文案双通道冗余(不靠颜色单独承载);棋盘面拒绝(动作不合棋盘规则,目标格边框闪)与资源面拒绝(行动点或储备不足,数字脉冲)必须可区分。图例面板恒可随时打开回看,不设解锁门。其三,**确认弹窗的按钮位次定值**:推进操作的钮在左、维持现状的钮(取消/留下/否)在右,三钮形态的次推进钮居中;全项目确认弹窗共用同一件、同一序,四处消费方(删档确认/营地大门有断点的三出口/营地大门无断点是否开局/篝火离场)皆然。**位次不随文案变**,语义由文案承担。 ### 呈现层订阅纪律:界面不自持状态、订阅面只留两条线 diff --git a/design/gdd/ux/camp.md b/design/gdd/ux/camp.md index 991baf70..0c81f411 100644 --- a/design/gdd/ux/camp.md +++ b/design/gdd/ux/camp.md @@ -1,6 +1,6 @@ # 营地场景 UX spec(camp) -> 状态:v2.7(现行) +> 状态:v2.8(现行) > 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制) > 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.md) > 相邻:[level-map.md](level-map.md)(主菜单语境) @@ -38,7 +38,7 @@ ## 顶栏 -钥石余额常驻画面顶部一行小 HUD 栏(高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**)。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。 +钥石余额常驻画面顶部一行小 HUD 栏:高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**。栏体底=营地头带深棕 `#6E4526`(token `CAMP_HEADER_BAND`,色值出处 [art-bible](../../art/art-bible.md) §7.2 五 token 对齐表)× **不透明度 0.80**——半透明系定值,底下画面透出 20%。栏内**只放钥石徽章一件**,右位、顶格落座(徽章自高 48px=栏高,无顶距可言),左位不放件。验收:目检〈顶栏成一条可辨窄带、徽章落在带内〉+机检〈栏体呈色对其下方落位带底色对比 ≥3:1〉。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。 ## 叠层与开合 diff --git a/待办清单.md b/待办清单.md index fe0f38d3..c7de721a 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,19 +2,16 @@ 本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 -#### 营地交互修复批——大门无断点开局回主菜单/确认框钮序/营地顶栏形态(2026-11-05 登记,来源=会话提及) +#### 营地交互修复批——大门无断点开局被开始层盖住/确认框钮序/营地顶栏无栏体(2026-11-05 登记,来源=会话提及) -**实况**:三件同批解决、互不依赖素材,后续拆卡不合卡。 -- bug 证据①无断点开局回主菜单:`game/presentation/camp_shop_presenter.gd:85`(`on_camp_closed()` 无条件显开始层)→ `game/presentation/main.gd:211-212`/`223-224`(大门两出口先关营地再开局)→ `main.gd:292-311`(`_start_adventure()` 无隐藏语句);该路径测试与探针零覆盖(启动全走 `_start_adventure_from_menu()`) -- bug 证据②确认框否左是右:`game/presentation/views/confirm_dialog.gd:31-39`(`_init` 内 add_child 序=取消→次→确认;信号按按钮对象路由不按位次,换文案不改位次) -- bug 证据③营地顶栏无栏体:`game/presentation/views/camp_hud_band.gd:10-17`(透明 Control 只挂右上钥石徽章) -- 相关文档 [营地屏 UX spec](design/gdd/ux/camp.md) v2.7 §顶栏(只定高 48px/全宽/浮层覆盖,未定底色·内容排布·徽章落位) - -**缺口**:camp.md §顶栏 段待补(底色/内容排布/徽章落位);确认框目标钮序待裁(确认左/确认右)。 +**实况**:三件同批一张需求卡片,改动—验收对照表内一件一行。设计定稿=design/gdd/ux/camp.md v2.8 §顶栏、design/gdd/systems/表现与支撑/HUD框架.md §提示与弹层、design/art/art-bible.md v3.43 §6.2。 +- bug 证据①无断点开局被开始层盖住:`game/presentation/camp_shop_presenter.gd:85` 显开始层 → `game/presentation/main.gd:211-212`/`223-224` 先关营地再开局 → `main.gd:292` `_start_adventure()` 不隐藏;该动线测试与探针零覆盖 +- bug 证据②确认框否左是右:`game/presentation/views/confirm_dialog.gd:31-39` 加入序=取消→次→确认 +- bug 证据③营地顶栏无可见栏体:`game/presentation/views/camp_hud_band.gd:10-17`(件已装配落座,缺栏体底与落座定值) **改动面**:文档+游戏代码 -**状态**:待文档设计 +**状态**:文档设计已闭合+待写卡 #### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(2026-10-28 登记,来源=会话提及) From 4a650d0ed3a495609a9efd3ca55c95e4277f4ba9 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 23:56:38 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E5=86=99=E5=8D=A1(=E8=90=A5=E5=9C=B0?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E4=BF=AE=E5=A4=8D=E6=89=B9=C2=B7=E4=B8=80?= =?UTF-8?q?=E5=BC=A0=E5=8D=A1=E4=B8=89=E9=9C=80=E6=B1=82=E7=82=B9):=20repo?= =?UTF-8?q?rts/=E9=9C=80=E6=B1=82=E5=8D=A1=E7=89=87-=E8=90=A5=E5=9C=B0?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E4=BF=AE=E5=A4=8D=E6=89=B9-2026-11-05.md=20?= =?UTF-8?q?=E6=88=90=E6=96=87=E2=80=94=E2=80=94=E4=BB=B61=20=E5=BC=80?= =?UTF-8?q?=E5=B1=80=E8=A2=AB=E5=BC=80=E5=A7=8B=E5=B1=82=E7=9B=96=E4=BD=8F?= =?UTF-8?q?(=E5=A5=91=E7=BA=A61=20=E9=99=90=E5=BC=80=E5=B1=80/=E7=BB=AD?= =?UTF-8?q?=E7=8E=A9=E4=B8=A4=E8=B7=AF=E5=BE=84=E7=9A=84=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E7=82=B9=E6=94=B6=E4=B8=80=E5=A4=84+=E6=91=98=20main.gd:194=20?= =?UTF-8?q?=E5=86=97=E4=BD=99=E5=8F=A5)=E3=80=81=E4=BB=B62=20=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E9=92=AE=E5=BA=8F=E9=95=9C=E5=83=8F(=E6=8E=A8?= =?UTF-8?q?=E8=BF=9B=E2=86=92=E6=AC=A1=E2=86=92=E7=BB=B4=E6=8C=81,?= =?UTF-8?q?=E5=9B=9B=E5=A4=84=E5=85=B1=E7=94=A8=E4=BB=B6=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E5=BA=8F,=E6=88=90=E5=91=98=E4=B8=8E=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E9=9B=B6=E5=8A=A8)=E3=80=81=E4=BB=B63=20=E9=A1=B6=E6=A0=8F?= =?UTF-8?q?=E5=8A=A0=E5=8F=AF=E8=A7=81=E6=A0=8F=E4=BD=93(=E8=89=B2?= =?UTF-8?q?=E5=80=BC=E5=81=9A=E6=88=90=20UiKit=20=E5=B8=A6=20=CE=B1=20?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=E5=B8=B8=E9=87=8F=20CAMP=5FHUD=5FBAND=3DColo?= =?UTF-8?q?r(CAMP=5FHEADER=5FBAND,0.80),=E7=9F=A9=E5=BD=A2=E7=94=B1?= =?UTF-8?q?=E9=A1=B6=E6=A0=8F=E4=BB=B6=E8=87=AA=E5=BB=BA);=20=E5=AF=B9?= =?UTF-8?q?=E7=85=A7=E8=A1=A8=20R1-R8=20=E4=B8=83=E6=A0=BC=E9=BD=90+?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E5=87=BA=E5=A4=84=E8=A1=8C=E9=94=9A+?= =?UTF-8?q?=E8=83=BD=E5=8A=9B=E8=A1=8C(=E4=B8=A4=E6=9E=9A=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=83=BD=E5=8A=9B=E5=90=8C=E6=89=B9=E5=85=A5=20game/t?= =?UTF-8?q?ests/README.md:162-163);=20=E5=8D=A1=E7=89=87=E5=A4=B4=E9=83=A8?= =?UTF-8?q?=E7=94=B3=E6=8A=A5=20=E9=AA=8C=E6=94=B6=E6=A1=A3=3D=E4=B8=AD?= =?UTF-8?q?=E3=80=81=E8=AE=BE=E8=AE=A1=E4=BA=8B=E5=AE=9E=E6=BA=90=E4=B8=89?= =?UTF-8?q?=E5=A4=84=E5=AE=9A=E7=A8=BF(camp.md=20v2.8/HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=20=C2=A7=E6=8F=90=E7=A4=BA=E4=B8=8E=E5=BC=B9=E5=B1=82?= =?UTF-8?q?/art-bible=20v3.43)+=E8=89=B2=E5=80=BC=E8=90=BD=E7=82=B9?= =?UTF-8?q?=E5=AE=9A=E7=A8=BF=E4=B8=8E=E4=B8=A4=E9=A1=B9=20Godot=204.7.2?= =?UTF-8?q?=20=E5=AE=9E=E6=B5=8B(const=20=E6=B4=BE=E7=94=9F=20const=20?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=80=9A=E8=BF=87=20to=5Fhtml=3Dtrue=20?= =?UTF-8?q?=E5=BE=97=206e4526cc;=E8=89=B2=E9=81=93=E5=8D=95=E7=B2=BE?= =?UTF-8?q?=E5=BA=A6=E4=BD=BF=20a=3D=3D0.80=20=E4=B8=BA=20false=20?= =?UTF-8?q?=E2=86=92=20=E6=96=AD=E8=A8=80=E7=A6=81=E6=B5=AE=E7=82=B9?= =?UTF-8?q?=E5=88=86=E9=87=8F=E7=9B=B4=E7=AD=89,=E6=94=B9=20to=5Fhtml=20?= =?UTF-8?q?=E6=AF=94=E5=AF=B9);=20=E4=B8=89=E8=BD=AE=E5=86=85=E6=94=B9?= =?UTF-8?q?=E7=9A=84=E5=9B=9B=E5=A4=84=E6=9E=B6=E6=9E=84=E8=BF=9D=E8=A7=84?= =?UTF-8?q?=3D=E2=91=A0R3=20=E6=92=A4"=E5=8D=87=E6=88=90=E5=91=98=E4=BE=9B?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9B=B4=E5=8F=96"(=E6=8A=B5=E8=A7=A6=20arch?= =?UTF-8?q?itecture.md:168=20=E8=A7=86=E5=9B=BE=E5=8D=95=E5=85=83=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3/:323=20=E7=A6=81=E7=9B=B4=E5=BC=95=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=86=85=E9=83=A8,=E6=94=B9=E5=85=AC=E5=BC=80=E5=AD=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=BA=8F)=E2=91=A1=E9=A1=B6=E6=A0=8F=20?= =?UTF-8?q?=CE=B1=20=E4=BB=8E=E8=A7=86=E5=9B=BE=E5=86=85=E6=8C=AA=E8=BF=9B?= =?UTF-8?q?=E5=85=AC=E5=85=B1=E4=BB=B6(=E4=BE=9D=20ui=5Fkit.gd:2=20?= =?UTF-8?q?=E5=85=A8=E7=AB=99=E5=8F=96=E8=89=B2=E5=94=AF=E4=B8=80=E5=87=BA?= =?UTF-8?q?=E5=8F=A3)=E2=91=A2=E5=A5=91=E7=BA=A64=20=E6=8E=AA=E8=BE=9E?= =?UTF-8?q?=E7=BA=A0=E6=AD=A3(=E7=A6=81=E6=96=B0=E5=A2=9E=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E8=89=B2=E8=80=8C=E9=9D=9E=E7=A6=81=E6=B4=BE=E7=94=9F?= =?UTF-8?q?=E5=B8=B8=E9=87=8F,=E4=BE=9D=20HP=5FBAR=5FTRACK:200=20=E6=94=B6?= =?UTF-8?q?=E5=8F=A3=E5=85=88=E4=BE=8B)=E2=91=A3=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=9D=A2=E8=A1=A5=20architecture.md:175=20CampHudBand=20?= =?UTF-8?q?=E8=A1=8C;=20=E4=B8=A4=E8=BD=AE=E7=8B=AC=E7=AB=8B=E5=A4=8D?= =?UTF-8?q?=E6=A0=B8=E5=85=A8=E8=90=BD=3D=E8=BD=AE=E6=AC=A11(22=20?= =?UTF-8?q?=E6=9D=A1:19=20=E9=87=87=E4=BF=A1=E8=90=BD=E5=8D=A1/2=20?= =?UTF-8?q?=E8=BD=AC=E7=94=A8=E6=88=B7=E8=A3=81=20Q1=20Q2/2=20=E6=9D=A1?= =?UTF-8?q?=E7=BB=8F=E5=86=99=E5=8D=A1=E6=AE=B5=E5=9B=9E=E8=AF=BB=E6=8E=A8?= =?UTF-8?q?=E7=BF=BB)+=E8=BD=AE=E6=AC=A12(5=20=E6=9D=A1:R3=20=E5=8F=8C?= =?UTF-8?q?=E9=92=AE=E6=96=AD=E8=A8=80=E5=BF=85=E7=BA=A2=E7=A1=AC=E4=BC=A4?= =?UTF-8?q?=E6=94=B9=20child(2)+=E5=AD=90=E8=8A=82=E7=82=B9=E6=95=B0?= =?UTF-8?q?=E9=9D=9E=E7=A9=BA=E5=89=8D=E7=BD=AE=E3=80=81R5=20=E8=A1=A5?= =?UTF-8?q?=E5=BC=80=E8=90=A5=E5=9C=B0=E5=89=8D=E7=BD=AE(camp=5Fscene=5Fvi?= =?UTF-8?q?ew.gd:51=20=E5=88=9D=E5=A7=8B=E9=9A=90)=E3=80=81T3.0=20?= =?UTF-8?q?=E8=A1=A5=E5=85=A5=E6=A0=91=E5=90=8E=E8=AF=BB=E6=95=B0=E6=97=B6?= =?UTF-8?q?=E6=9C=BA=E3=80=81=E8=A1=8C=E5=8F=B7=E5=85=AB=E5=A4=84=E4=B8=8E?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E9=93=BE=E7=A1=AE=E8=AE=A4=E3=80=81Q1=20?= =?UTF-8?q?=E8=A3=81=E5=86=B3=E8=A7=A3=E9=87=8A=E9=9D=A2=E6=8C=89=E8=87=AA?= =?UTF-8?q?=E5=88=A4=E8=90=BD=E5=B9=B6=E5=90=91=E7=94=A8=E6=88=B7=E6=8A=A5?= =?UTF-8?q?=E5=A4=87);=20=E5=90=8C=E6=89=B9=E7=99=BB=E8=AE=B0=E4=B8=A4?= =?UTF-8?q?=E6=9D=A1=E5=B7=A5=E7=A8=8B=E6=94=B9=E5=8A=A8=E5=BE=85=E5=8A=9E?= =?UTF-8?q?(=E8=AE=A4=E9=94=9A=E5=BC=8F=E4=B8=8D=E5=90=AB=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E6=AE=B5=20pipeline=5Fgate.py:68;=E5=B1=82=E5=BA=8F?= =?UTF-8?q?=E8=90=BD=E4=BD=8D=E5=8D=95=E5=8F=A3=E6=89=AB=E6=8F=8F=E9=9D=A2?= =?UTF-8?q?=E4=B8=8D=E5=90=AB=E5=AD=90=E7=9B=AE=E5=BD=95=20source=5Fguard?= =?UTF-8?q?=5Ftest.gd:85-99,=E5=9D=87=E5=B7=A5=E5=85=B7=E4=BB=A3=E7=A0=81/?= =?UTF-8?q?=E5=BE=85=E6=89=A7=E8=A1=8C);=20=E5=BE=85=E5=8A=9E=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E6=9C=AC=E6=9D=A1=E7=8A=B6=E6=80=81=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=AE=BE=E8=AE=A1=E5=B7=B2=E9=97=AD=E5=90=88=EF=BC=8B?= =?UTF-8?q?=E5=B7=B2=E5=86=99=E5=8D=A1;=20=E6=9C=AC=E6=AD=A5=E5=87=AD?= =?UTF-8?q?=E6=8D=AE:=20=E6=B5=81=E6=B0=B4=E7=BA=BF=E9=97=B8=E7=9B=B4?= =?UTF-8?q?=E8=B7=91=20PASS(=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE=2027/?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=8F=A3=E5=BE=84=202=20=E5=BC=A0=E5=85=A8?= =?UTF-8?q?=E8=BF=87)+tools/run=5Fall=5Fgates.sh=20--docs=20exit=3D0?= =?UTF-8?q?=E3=80=8C=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87,=E9=97=B8?= =?UTF-8?q?=E5=86=B5=E4=B8=8E=E5=9F=BA=E7=BA=BF=E4=B8=80=E8=87=B4=E6=97=A0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BA=A2=E3=80=8D(=E5=9B=9E=E6=89=A7=20domai?= =?UTF-8?q?n=3Ddocs=20=E5=9F=BA=E7=BA=BF=2000d7bbfb);=20=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E9=99=84=E8=AE=B0:=20=E7=94=A8=E8=AF=AD=E9=97=B8=20doc=5Fterm?= =?UTF-8?q?=20=E6=9B=BE=E6=8A=8A=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E9=87=8C=E6=96=87=E4=BB=B6=E5=90=8D=20ui=5Fregistry.gd=20?= =?UTF-8?q?=E7=9A=84=E5=AD=90=E4=B8=B2=20registry=20=E5=88=A4=E4=B8=BA?= =?UTF-8?q?=E9=80=80=E5=BD=B9=E8=AF=8D=E7=96=91=E4=BC=BC=E7=8E=B0=E8=A1=8C?= =?UTF-8?q?(=E5=9F=BA=E7=BA=BF=E7=BB=BF=E2=86=92=E7=8E=B0=E7=BA=A2),?= =?UTF-8?q?=E5=B7=B2=E6=94=B9=E4=BB=A5=E5=87=BD=E6=95=B0=E5=90=8D=20apply?= =?UTF-8?q?=5Flayer=5Fcontract()=20=E4=BD=9C=E9=94=9A=E9=81=BF=E5=BC=80?= =?UTF-8?q?=E8=AF=AF=E6=8A=A5=E3=80=81=E5=8F=AF=E6=A3=80=E7=B4=A2=E6=80=A7?= =?UTF-8?q?=E4=B8=8D=E4=B8=A2,=E8=AF=A5=E8=AF=AF=E6=8A=A5=E5=B1=9E?= =?UTF-8?q?=E9=97=B8=E4=BB=B6=E9=9D=A2=E5=8F=A6=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/README.md | 2 + reports/复核-营地交互修复批-2026-11-05.md | 62 +++ reports/需求卡片-营地交互修复批-2026-11-05.md | 381 ++++++++++++++++++ 待办清单.md | 34 +- 4 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 reports/复核-营地交互修复批-2026-11-05.md create mode 100644 reports/需求卡片-营地交互修复批-2026-11-05.md diff --git a/game/tests/README.md b/game/tests/README.md index d03ccf0f..524974e4 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -159,6 +159,8 @@ tests/ | 营地大门两分支确认与断点续玩接线 | 营地交互流 | 大门命中区 → 合成鼠标事件经 `push_input` 真点(`ProbeUiUtil.push_click_at`)→ ConfirmDialog 真钮(三出口)→ `main._on_confirm_accepted()`(新冒险确认)/`main._resume_from_menu()`(断点续玩) | `ui_click_probe.gd:_ready`(大门两分支段/断点确认段——真输入 push_click 驱动,2026-11-05 真输入化);`presentation_encapsulation_sweep_test.gd:_init`(迭代52卡③组合根交接面结构性主证——pres封装 V1–V4 四组私取零命中+新公开面正锚,2026-11-05 补链锚) | | 四柜台拆解后五界面入口改挂与写口零回归 | 营地五界面 | 五界面意图信号 → `CampShopPresenter` → `MetaProgression.tree_buy()` 写口 | `forge_workbench_test.gd:_run`(承载类同迁断言十二项);`ui_click_probe.gd:_ready`(写口链段) | | 营地篝火回主菜单与笔记本改挂动线 | 营地交互流 | 笔记本命中区 → `CareerOverlay.open()`;篝火命中区 → ConfirmDialog 二次确认 → 主菜单 | `ui_struct_probe.gd:_ready`(career 段);`screen_flow_probe.gd:_ready`(篝火/开合段) | +| 确认弹窗按钮位次与语义同钉 | HUD框架 | `ConfirmDialog.open()`/`ConfirmDialog.open_three()` 开态读按钮行子节点序(定值=推进钮在左/次推进居中/维持钮在右,四处共用件同一序);探针在弹窗开态按左位钮中心坐标 `ProbeUiUtil.push_click_at` 真点 | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order`;`ui_click_probe.gd:_ready`(大门无断点段左位坐标真点) | +| 营地顶栏栏体定值与落座 | 营地场景视图 | 真实工程 boot 后经装配链 `MainUiBuild`→`CampSceneView.set_hud_band()` 读 `ui.modals.camp.scene.hud_band`(定值=`CAMP_HEADER_BAND`×α0.80、48px 全宽浮层、徽章右位顶格);单元经 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口读背景贴图落位带像素算对比 ≥3:1 | `system_test.gd:_smoke_camp_hud_band`;`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` | | 营地图鉴占位挂起态与反参照结构纪律 | 营地场景视图 | 布告板命中区 → `CampSceneView._gui_input()`(object_pressed 信号) → 图鉴占位视图;营地根态场景树审计 | `ui_struct_probe.gd:_ready`(占位禁词/禁行段);`ui_struct_probe.gd:_ready`(结构禁词段) | | 营地刷新单口与场景根 HUD 同步 | 营地刷新总线 | 写口尾 → `CampRefreshBus.refresh()` → 现开界面刷新+场景根 HUD 刷新 | `ui_struct_probe.gd:_ready`(顶栏位段);`ui_click_probe.gd:_ready`(写口尾刷新段) | | Esc 浮窗唤起与三出口动线 | 营地 Esc 流 | 营地场景根态合成 Esc 键事件→ `main_esc_flow.on_esc_input()` → `ui.flow.open/close`;浮窗三钮 `pressed` → 三出口 | `screen_flow_probe.gd:_ready`(Esc 唤起/子界面开态不唤起/再按关窗/非营地不唤起段);`screen_flow_probe.gd:_ready`(返回动线/设置双路径段) | diff --git a/reports/复核-营地交互修复批-2026-11-05.md b/reports/复核-营地交互修复批-2026-11-05.md new file mode 100644 index 00000000..a19071b4 --- /dev/null +++ b/reports/复核-营地交互修复批-2026-11-05.md @@ -0,0 +1,62 @@ +# 复核报告 · 营地交互修复批需求卡片(2026-11-05) + +> 被复核对象:`reports/需求卡片-营地交互修复批-2026-11-05.md`(v1.0 成文态) +> 复核者:未参与撰写的只读子代理(独立会话,禁写文件、禁跑闸、禁运行游戏测试),任务=对卡内每一处行号、契约、判据逐条开火 +> 复核轮次:1(第 16 节逐条登记去向与处置;本报告留发现原文与依据索引) +> 复核对象工作副本:`D:/code/g-plan2-camp-interact`(分支 camp-interact-20261105) + +## 复核者总判原文(摘录) + +> 按现状不能进实现段。缺四块闭合:①契约1 收窄措辞(否则实现首帧即自触 L3);②R3/R4 的按钮行句柄定义+§12 受控例外改判(否则判据落不了码);③T3.1 的 `UiAssembler` 引用形态+T3.2 内缩实现与 R5 的 x 向判据(否则 12px 无机制、断言可假绿);④R6 呈色输入来源改绑视图实件(否则撞 §8.7 第 5 条判红)。另有 6 处纯事实订正可自判直改。T 卡估时合计 0.25+0.5+0.75+0.5=2.0 与「≈1.75–2.0」自洽;四处消费方与 HUD框架:90 语义映射核过无冲突。 + +## 发现与依据索引(22 条) + +| # | 卡内位置 | 发现 | 复核者给的依据 | 去向 | 写卡段处置 | +|---|---|---|---|---|---| +| 1 | §4 契约1 | 「隐藏点只许两处」按字面过宽,仓库另有 5 处以上 | `camp_shop_presenter.gd:43`、`main_overlay_flow.gd:8/41/95`、`main_debug_flow.gd:88` | 退回上游(复核者判) | 改自判:契约1 收窄为「限开局/续玩路径」,12 处显隐点逐条列名。定名不采纳——卡内措辞问题,非规范冲突 | +| 2 | §3.1 | `_start_adventure()` 行号段错(写 311) | `main.gd:308`=`return`,`:311`=下一函数声明 | 自判 | 改 292-308 | +| 3 | §9 T1.1 | 失败分支的开始层终态变化未申报 | `main.gd:306-307` `fatal_screen` | 补写 | §3.1 补终态申报 | +| 4 | §8 R2/§9 T1.4 | 段内 await 1 帧不足以让选路停点成形 | `ui_click_probe.gd:703`;先例 `screen_flow_probe.gd:186-192` 用 `_frames(3)` | 补写 | T1.4 补帧口径+实测最小帧数入实现报告 | +| 5 | §8 R3 | `btn_row` 是局部变量,判据无法写 | `confirm_dialog.gd:27`(成员仅 `:13-16`) | 补写(阻塞) | T2.1 升成员 `_btn_row`;R3 按 test-conventions §8.2 受控例外申报 | +| 6 | §12 | 自称不直取私有,实际 R3/R4 直取 | `confirm_dialog.gd` 成员表;`main._confirm_consumer` | 自判 | 改为受控例外口径,不再谎称 | +| 7 | §9 T2.3 | `:702` 真点已消耗 `gate_new`,R4 无左位可点 | 段结构 `ui_click_probe.gd:684-705`;`main.gd:215` 置空 | 补写 | T2.3 补前置重开步与坐标取法 | +| 8 | §10 L1 | `:679` 引用定性错;直发面在 `:697`;归属应系 §8.3 而非契约7 | 复核者回读 | 自判 | **部分不采信**:`:679` 经写卡段回读确为段头注「大门两分支断言段」,断点注入起 `:706`;直发行号实为 `:696`(复核者写 697 亦偏);§8.3 归属采纳。附带自查纠正 R8 的次钮断言行 `:696`→`:694` | +| 9 | §9 T3.1/§12 | `UiAssembler` 无 `class_name`,`views/` 零引用先例,卡内调用写法不成立 | `ui_assembler.gd:1-5`(`extends RefCounted`+头注「供组合根接线」)、引用方 `main_ui_build.gd:7`/`scenes/adventure_screen.gd:21`/`scenes/menu_screen.gd:15`、`views/` 内联先例 `battle_end_overlay.gd:261/289/314` | 待裁 | 第 15 节 Q1 两案并列;T3.1 改「待 Q1 后落」 | +| 10 | §3.3/§8 R5 | 徽章自高 48 系推导,`panel_flat` `border_w=1` 若计入则 50 | `ui_kit.gd:640`+`panel_flat` `ui_kit.gd:455-464`(复核者自标「这是推测,未跑批」) | 补写 | T3.0 新增前置读数;≠48 走 L3 第 5 条 | +| 11 | §9 T3.2/§11.3 | 12px 内缩无可达机制(宽度随位数变);R5 缺 x 向判据=改了仍绿 | `camp_hud_band.gd:16`(现值写死 x)+`:22`(文案「钥石 %d」) | 待裁+补写 | Q2 三案并列;R5 已补 `end.x == 栏 end.x − 12` | +| 12 | §8 R6 | 呈色若取自实现表达式=oracle 镜像 | test-conventions §8.7 第 5 条 | 自判 | R6 改规格侧写死值,禁喂视图件 color | +| 13 | §11.4 | 「4×8 步长沿该件口径」错,该件全像素扫描 | `terrain_tile_sets_test.gd:105-107` | 自判 | 白名单第 4 条改全扫描并声明旧口径作废 | +| 14 | 数学校核 | 全扫与抽样均色皆 RGB(223,235,232)(卡 `#DEEAE7` 差 ≤1/255);叠后 4.29:1、徽章对天空 1.30:1;落位带 0–48 只含背景成立 | `camp_scene_layout.tres:9`(背景 `Rect2(0,0,1280,800)`)、围墙 y=261、大门 y=250;`project.godot:30` 视口宽 1280 | 自判(确认+读数微差) | 差值落 L2 容差,阈值不放宽;卡内读数保留 | +| 15 | §1/§5 | 「七行」与对照表实数 R1–R8 不符 | 卡内自相 | 自判 | 改八行 | +| 16 | §5 | 引「第 1 节行为表」——卡内无该表(悬空引用) | 卡内自相 | 自判 | 改自足表述 | +| 17 | §7 | 结构图验收行缺 R7/R8 | 卡内自相 | 补写 | 两行补齐 | +| 18 | §13/§10 L3 第4条 | gdstyle 不扫 tests,新件三查与互斥条对新件无触发路径;套件入口是 `_initialize()` 非 `_init` | `tools/gdstyle_gate.py:45`(SCAN_DIRS 三目录)+头注 `:8`(probe/sim/tests 排除);`camp_scene_layout_test.gd:12` | 自判 | §13 改「对本件不适用」;L3 第 4 条限定 presentation 面;T2.2 入口名与先例行号订正 | +| 19 | §13 存量一查 | 核过无误:基线 `:11`=32、`:12`=23;`main.gd` 公开方法 31+1 静态=32 持平 | `tools/gdstyle_baseline.txt` | 自判(确认类) | 无改动,留档已核 | +| 20 | §4 契约4 依据 | 称五 token 对齐机检「查无该件」(`palette_check.py` 无 CAMP_HEADER 逻辑,全仓只命中视图用法) | 复核者 grep | 退回上游(复核者判) | **不采信其结论**:写卡段回读 `tools/palette_check.py:22` 有 `ui_tokens.png`=ui_kit 全量 Color8 token 快照基线,机制存在但不逐名比对表内五行;契约4 依据改成该准确口径 | +| 21 | §9 T2.2 | 册为硬编码清单,「接进套件册」不够具体 | `game/tests/run_all.gd:166` 起条目形态 `{"path":…, "expect":0}`+行尾注 | 补写 | T2.2 写死条目全文 | +| 22 | 横面 | `ANCHOR_RE` 字符类不含中文段→中文名现行文档无法被机检认锚 | `tools/pipeline_gate.py:68` | 退回上游 | 列 §6 末行;本批不动闸件,是否登记归用户发令 | + +## 采信统计与残余 + +- 采信并落卡:19 条(自判 11/补写 8)。 +- 转待裁(不代用户选):2 条(#9、#11 的机制案),落第 15 节 Q1/Q2。 +- 不采信结论:2 条(#8 的 `:679` 定性、#20 的「机检不存在」),均经写卡段逐行回读推翻,依据记在表内。 +- 残余(本卡范围外):#22 机检件缺口;以及本批既有的「art-bible §4.5 #7 破坏性确认异位是否追认」——早于本卡在册,见第 5 节不做表。 + +**卡片状态**:轮次 1 后为「待裁未清零」;轮次 2 后见下节。 + +--- + +## 轮次 2(Q1/Q2 裁决落卡之后,另起一个未参与撰写的只读子代理) + +任务书限定只查五处,且每处须给 `文件:行号` 依据。发现五条: + +| # | 卡内位置 | 发现 | 依据 | 去向 | 处置 | +|---|---|---|---|---|---| +| 1 | §8 R3/§9 T2.2 | 双钮形态断言「child(1)=维持」必红——次钮只 `visible=false`、不移除 | `confirm_dialog.gd:47` | 补写 | 改 child(2) + 子节点数=3 非空前置(已落卡) | +| 2 | §8 R5 | 未写「须先开营地」前置,boot 态营地屏为隐读不到尺寸 | `camp_scene_view.gd:51` | 补写 | 真实入口补 `open_camp()`+await 排版帧(已落卡) | +| 3 | §9 T3.0 | PanelContainer 最小尺寸入树排版后才可信,刚 `new()` 读数不可信,时机未写 | `ui_kit.gd:634-656` | 补写 | 改为经公开句柄读在树的现成实例、await 后再读(已落卡) | +| 4 | §3/§8 确认类 | 八处行号全对;`to_html(true)` 追加 α 两位、`6e4526cc` 串对;1280×48 尺寸链可达;「按类型找第一个 HBoxContainer」在本弹窗树内唯一 | `main.gd:211-212`/`:223-224`/`:292-308`、`ui_kit.gd:640`、`ui_assembler.gd:100/214/224-230`、`ui_kit.gd:189/190/200`、`standee_present_values_test.gd:36`、`scenes/camp_screen.tscn:7-11`、`project.godot:30-31` | 自判(留档) | 无改动 | +| 5 | §15 Q1 | 裁决句字面只覆盖「色值放公共层」这条轴,「矩形谁建」是写卡段的展开 | 对话原话序列:写卡段推荐句为「色值进公共件、矩形留在顶栏件自建」,用户回话「那没有问题,就按放在公共区域的这一层的方案来实现」 | 待裁(复核者判)→ 按自判落并报备 | 卡内第 15 节与第 16 节各写入解释面一句;用户不认该读法即重开,重开范围仅止于矩形建造方 | + +轮次 2 总判:三条补写已落卡,一条确认留档,一条按自判落并向用户报备。落卡后本卡零待裁点,可提交(合回仍等用户发令)。 diff --git a/reports/需求卡片-营地交互修复批-2026-11-05.md b/reports/需求卡片-营地交互修复批-2026-11-05.md new file mode 100644 index 00000000..2cc32f4f --- /dev/null +++ b/reports/需求卡片-营地交互修复批-2026-11-05.md @@ -0,0 +1,381 @@ +# 需求卡片 · 营地交互修复批(2026-11-05) + +> **来源**:待办清单条目「营地交互修复批——大门无断点开局被开始层盖住/确认框钮序/营地顶栏无栏体」(2026-11-05 登记,来源=会话提及) +> **唯一事实源**:`design/gdd/ux/camp.md` v2.8 §顶栏 · §点选反馈 + `design/gdd/systems/表现与支撑/HUD框架.md` §提示与弹层(三条纪律·其三) + `design/gdd/systems/冒险循环/关卡节点地图.md` §核心循环位置 + `design/art/art-bible.md` v3.43 §6.2 +> 验收档:中(三件做错的后果都是玩家可见但不损持久状态——呈现与交互逻辑;不触存档写读、不触结算公式、不触 RNG 流。判据=本技能第 16 节单维失败代价) +> **改动面**:游戏代码(`game/` 表现层+测试面)。文档面已由文档批落定为现行规格,本卡不携任何 `design/` 改动。 +> **编排状态**:未编迭代(roadmap 现行最高编号=迭代 52,已完成;本卡待 iter-plan 段编排) + +--- + +## 1. 目标与完成定义 + +三件互不相干的营地屏交互缺陷,一批修完: + +1. **大门无断点开局被开始层盖住**——玩家点大门、按「是」之后,冒险其实已经开局,但主菜单层(开始层)重新显出来并把冒险画面盖住,玩家看到的是主菜单,误以为没开始。修完=从大门真实点击进入后,开始层不可见、关卡画面(节点图选路停点)可见。 +2. **确认弹窗按钮位次反直觉**——同一个共用弹窗件把「维持现状」那颗放在左、「推进」那颗放在右,四处消费方(删档/大门三出口/大门两出口/篝火)皆然。修完=整体镜像为「推进在左、次推进居中、维持在右」,且位次与语义的绑定有真点验收。 +3. **营地顶栏看不出是一行 HUD 栏**——顶栏件已装配、已落座、预留位 48px 全宽都在,缺的是那层看得见的底:现在是透明 Control 只挂一枚徽章,而徽章的烛光黄底对背后天空均色只有 1.29:1。修完=按 camp.md v2.8 §顶栏 定值画出栏体(`CAMP_HEADER_BAND` × 不透明度 0.80),徽章顶格右位落进栏内,并有尺寸与对比的机检。 + +**完成定义**:四条全落即算这批做完—— + +- 对照表(第 8 节)八行断言(R1–R8)在当轮真实跑批中执行且通过,无红项遗留; +- 第 8 节标注的两行新增能力已随写卡段落进 `game/tests/README.md`,实现批末核对名实相符; +- 回归面(第 5 节列的七条未改动动线)在跑批中仍绿,未因位次改动或栏体新增而失守; +- 末卡产出物(实现报告+反哺点清单)写齐,`reports/` 两份落位。 + +真人目检项(营地顶栏观感)登记进实现报告实机目检总清单即算义务了结,**不作合回前置**(AGENTS.md 红线:真人检测只登记不阻塞)。 + +--- + +## 2. 设计事实源 + +本节为卡片设计依据的唯一出处声明,与待办清单条目实况的闭合凭据同批一致: + +| 需求点 | 现行规格句 | 出处 | +|---|---|---| +| 件1 | 点大门永远先出确认,无未完冒险=是否开始新冒险 | `design/gdd/ux/camp.md:16` | +| 件1 | 关卡开始 → 呈现节点图选路停点(关卡画面;入口不自动进入) | `design/gdd/systems/冒险循环/关卡节点地图.md:78` | +| 件2 | 确认弹窗的按钮位次定值:推进钮在左、维持现状钮在右,三钮形态次推进居中,四处共用同一件同一序 | `design/gdd/systems/表现与支撑/HUD框架.md:90` | +| 件3 | 栏体底=`#6E4526`×不透明度 0.80、高 48px 全宽浮层、栏内只钥石徽章一件右位顶格、验收=目检窄带可辨+机检对落位带底色 ≥3:1 | `design/gdd/ux/camp.md:41` | +| 件3 | 色板五件对齐表在册色 `CAMP_HEADER_BAND #6E4526`(机检〈ui_kit 五 token 与本表一致〉) | `design/art/art-bible.md:452` | +| 件3 | 命中区不侵入顶栏禁区(y ≤ 48) | `design/gdd/ux/camp.md:37` | + +预览人审在案:2026-11-05 会话内实尺 HTML 预览(十张已过审场景层按 `camp_scene_layout.tres` 现值叠放+1:1 顶栏放大条+页内 WCAG 即时算式),用户审语「我看了预览的效果是OK的」,选定项=甲 深棕带 × 不透明度 0.80;快照文件 `chat-session-visualizations/…/visualizations/snapshot-444250310dc22234ac755b11fb31ce6885adb634b271c7083d53cc5d72b98640.html`(会话侧不可变快照,非仓库件)。像素级判据句已随附进 `camp.md` v2.8 §顶栏。 + +色值落点定稿(用户 2026-11-05 裁「按放在公共区域的这一层」):`ui_kit.gd` 新增一枚带透明度的派生常量 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`——红绿蓝三分量继承既有 token、α 只此一处,顶栏件引用它。两件事已实测(引擎 4.7.2,工程外临时工程跑通后即删):①这种「const 作构造参数派生 const」的写法编译通过,`to_html(true)` 读数为 `6e4526cc`、三分量与母 token 逐一相等;②**色道存单精度浮点,`.a == 0.80` 判为 false**(实测 `a=0.80000001192093`)——所以断言一律写成 `to_html(true) == "6e4526cc"` 或与同构造值整体等值比较,禁写 `.a == 0.80`,否则实现正确而断言红,最容易的自救就成了放宽阈值(触 test-conventions §8.5 禁止削弱测试)。 + +--- + +## 3. 现状事实(2026-11-05 实查;开工日自行复核行号) + +### 3.1 件1——开始层可见性无单点 + +- `game/presentation/camp_shop_presenter.gd:85`:`on_camp_closed()` 体内无条件 `start_overlay.visible = true`。该函数的本职是「回主菜单」,篝火离场(`game/presentation/main.gd:214`)与 Esc 返回主菜单(`game/presentation/main_esc_flow.gd:46`)都靠这句显出菜单——**语义正确,不是缺陷本体**。 +- `game/presentation/main.gd:211-212`(`_on_confirm_accepted()` 的 `"gate_new"` 分支)与 `game/presentation/main.gd:223-224`(`_on_confirm_secondary()` 的 `"gate"` 分支):两处都是先调 `camp_pres().on_camp_closed()`(把开始层显出来),紧接着调 `_start_adventure()`。 +- `game/presentation/main.gd:292-308`:`_start_adventure()` 全函数体(体止于 `:308` 的 `return`,`:311` 已是下一函数声明行)零处触碰 `start_overlay`——于是上面那句「显出来」没有对手句把它收回去。失败分支(`main.gd:306-307` `fatal_screen`)在 T1.1 落笔后开始层终态由「可见」变「隐藏」,该终态差在本卡第 5 节明示为可接受(致命屏自会接管,无菜单可用)。 +- 既有先例=每条启动路径在自己函数内部自管开始层:`game/presentation/main.gd:194`(`_start_adventure_from_menu()` 内先隐藏再转调)、`game/presentation/main.gd:246`(`_resume_from_menu()` 内隐藏)。所以现状的隐藏点是散在入口层的两处,缺的是开局那一条。 +- `_start_adventure()` 全库调用点仅三处(`main.gd:195`/`212`/`224`),无一需要开局后保留开始层。 +- 层序事实:`game/tests/system_test.gd:87-89` 断言「start_overlay 在冒险屏壳之上(盖住战斗内容)」——被盖住的正是冒险画面,故真机观感=「画面回主菜单」。 +- 覆盖缺口事实:`game/presentation/main.gd:210` 的 `"gate_new"` 与 `main.gd:223` 的次确认分支,现有断言只核 `run_scope.is_adventure` 与营地场景屏隐藏(`game/probe/ui_click_probe.gd:703-705`),**没有一条核开始层可见性**,故缺陷在绿批里静默存活。 + +### 3.2 件2——弹窗件位次写死 + +- `game/presentation/views/confirm_dialog.gd:31-39`:`_init()` 内三颗钮的 `btn_row.add_child()` 次序=取消 → 次 → 确认。 +- 信号按按钮对象路由、不按位次:`confirm_dialog.gd:32/35/38` 各自 `.pressed.connect(_emit_cancelled/_emit_secondary/_emit_confirmed)`——换序不改路由,文案也不用来认钮。 +- 四处消费方(全部经同一件):删档确认 `game/presentation/main_overlay_flow.gd:129`(钮标「删除」/「取消」)、大门有断点三出口 `main_overlay_flow.gd:76-78`(「继续上一段冒险」/「开始新冒险」/「取消」)、大门无断点 `main_overlay_flow.gd:82`(「是」/「否」)、篝火离场 `main_overlay_flow.gd:72`(「离开」/「留下」)。 +- 现有断言只核「次钮在两出口形态下隐藏」(`ui_click_probe.gd:696`),**无一条核位次**。 + +### 3.3 件3——顶栏件有装配无栏体 + +- `game/presentation/views/camp_hud_band.gd:10-17`:`_init()` 设 `PRESET_TOP_WIDE` + `custom_minimum_size=(0,48)` + `mouse_filter=MOUSE_FILTER_IGNORE`,体内只挂一枚徽章,**无任何底色件**——即整条顶栏是透明的。 +- 徽章现状:`camp_hud_band.gd:15-16` 设 `PRESET_CENTER_RIGHT` 后 `position=Vector2(-160, 8)`(注释自标「代拍板 3」)。 +- 徽章自高=48px:`game/presentation/views/ui_kit.gd:640` 的 `panel_flat(..., Vector4(14, 8, 14, 8))` 给图标 32px 上下各加 8px 内边距=48px,正等于栏高——**顶距 8px 在栏内放不下**,落座只能顶格。 +- 装配链在位:`game/presentation/main_ui_build.gd:120-121` 造件并调 `set_hud_band()` → `game/presentation/views/camp_scene_view.gd:88-91` 把件塞进 `_hud_slot` → `camp_scene_view.gd:188` 预留位 `custom_minimum_size=(0, layout.hud_band_h)`,取数自 `game/data/tables/camp_scene_layout.tres` 的 `hud_band_h=48`(schema 见 `game/data/schemas/camp_scene_layout_table.gd:7`)。 +- 铺满矩形的既有实现盘上已有两份、形态不同:`game/presentation/ui_assembler.gd:224-230` `overlay_bg(host, color) -> ColorRect`(**外部调用者零**,仅被该文件自身 `:100`、`:214` 用)与 `game/presentation/views/ui_kit.gd:486-491` `_modal_dim()`(私有,仅 `:511` 一处用)。四处同形的用途实为三种意图——亮底面板(`BG_BRIGHT`)/模态压暗遮罩(黑 0.6)/tooltip 遮罩底,本批顶栏带是第四种意图(常驻 HUD 条且不吃点击)。**本批不复用那一份、也不抽新工厂**(依据与留痕见第 12 节与第 15 节);顶栏件按 `views/` 现行形态自己新建矩形——该目录直接 `ColorRect.new()` 的现存先例共 8 个文件 15 处(`battle_end_overlay.gd` 4 处、`board_fx_view.gd` 3 处、`camp_scene_view.gd` 1 处等)。 +- 落位带实际背景:营地背景层 `game/assets/art/scene/scene_camp_bg.png` 的 y0–48 带均色实测 `#DEEAE7`(逐像素取样,画面最上的可点物件是大门,顶缘 y=250,故 48px 带整条落在远景天空上)。徽章烛光黄底 `#F6C667` 对该天空 1.29:1;定值 `#6E4526`×0.80 叠后呈色 `#84664D`,对天空 4.27:1、对徽章底 3.31:1。 +- 像素取样先例:`game/tests/unit/presentation/terrain_tile_sets_test.gd:107` 已用 `img.get_pixel()` 在单元套内取样贴图。 +- 无 golden 锚:`game/tests/golden/README.md`「未建锚面备案」明写营地屏无改前 golden 锚,该面等价证据=探针/单元断言+实机目检。 + +--- + +## 4. 设计契约(红线,违反=L3 停机呈报) + +- 🔴 **契约1|冒险启动路径的隐藏点收一处**:**限「开局」与「续玩」两条路径**——隐藏责任只许落在 `_start_adventure()`(开局)与 `_resume_from_menu()`(续玩)两个函数内部。第 3.1 条的先例形态即本契约依据。**本契约不管其余开层自管点**:全库开始层显隐共 12 处(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`),那些是选档层/生涯页/终局回菜单等各自开合面的自管句,本批一律不动、也不受本契约约束。禁再新增**开局或续玩路径**上的第三处隐藏点,禁把隐藏写回 gate 出口(调用方)。 +- 🔴 **契约2|`on_camp_closed()` 语义不动**:`camp_shop_presenter.gd:85` 那句显示开始层是本批禁改项——篝火离场与 Esc 返回主菜单两条动线靠它。改动=回退主菜单功能。 +- 🔴 **契约3|位次改动只动 `add_child` 次序**:`confirm_dialog.gd:31-39` 的三句连接(`.pressed.connect(...)`)、三个钮对象的创建与文案参数一律不动;四处消费方的 `open()`/`open_three()` 传参零改动。位次与语义的绑定由第 8 节 R4 行的坐标真点断言钉。 +- 🔴 **契约4|取色单点在 UiKit,禁视图内裸写色值**:栏体色=`ui_kit.gd` 新增的派生常量 `CAMP_HUD_BAND`(`Color(CAMP_HEADER_BAND, 0.80)`,三分量继承在册 token),顶栏件与断言都只引用它;**禁在视图件写 `Color8(110, 69, 38)` 类裸值、禁把 α 写在视图里**。禁的是新增**语义色**,不是禁止派生常量——同文件在册先例:`HP_BAR_TRACK`(`ui_kit.gd:200`,注「视图原裸值收口单点——非色板新增」)、`TOAST_BG`(`:189`)与 `TIP_BG`(`:190`)都是带 α 的派生常量。依据=`ui_kit.gd:2` 文件头「呈现层共享小部件工厂+全站取色/描边唯一出口——各视图禁内联 Color8/StyleBoxFlat 字面量,换皮只改本文件」。色板基线不受本枚常量影响:`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 形态从不进快照。 +- 🔴 **契约5|栏体不挡命中**:栏体 ColorRect 必须 `mouse_filter = Control.MOUSE_FILTER_IGNORE`,顶栏件自身 `camp_hud_band.gd:13` 的同款设置是既有锚。漏设=48px 带内的点击被栏体吃掉,与 `camp.md:37` 禁区纪律叠加成「顶排物件点不动」。 +- 🔴 **契约6|本批禁写文档面**:`design/` 整个目录、`待办清单.md`/`体验验证清单.md` 两份根流程清单、`game/tests/README.md` 一律禁改(能力两行已由写卡段落表,实现批只改名实相符);产出物只准落 `reports/`。 +- 🔴 **契约7|禁变异类测试与撤销类取证**(test-conventions §8.4,全项目红线):本批所有断言从真实入口进入、绑被测改动特有的可观测提交面,禁人为制造红、禁回退 hunk 取证。 + +--- + +## 5. 范围边界 + +**做**: +1. `_start_adventure()` 入口收一个隐藏点,摘掉 `main.gd:194` 的冗余句; +2. `ConfirmDialog` 三颗钮的 `add_child` 次序整体镜像; +3. `ui_kit` 加一枚带 α 的派生色值常量 + `CampHudBand` 用它新建可见栏体、徽章改右对齐容器顶格落座; +4. 上述三件各自的测试(对照表 8 行 R1–R8)+能力清单两行(写卡段已落)+回归面确认。 + +**不做(含去向)**: + +| 不做的东西 | 为什么 | 去向 | +|---|---|---| +| 「破坏性确认与常规确认禁止同位」(art-bible §4.5 清单 #7 后半句/interaction-patterns §7)在弹窗层的闭合 | 该句系 2026-09-14 文档骨架初版提交随件进入,全库查无用户裁决记录,且 battle.md:335 指定的无障碍基线权威(HUD框架 §可访问性基线)不含它——它是不是用户定的规则,本身待裁 | 本卡不动它;现状四处同位属既有状态,不随本批产生或消失。要不要闭合,归用户另发令,另立待办条目 | +| 顶栏栏内加第二件(存档位号/关卡语境等) | 用户 2026-11-05 裁「栏内只放钥石徽章一件」,左位不放件 | 无 | +| 营地场景层坐标、素材、七物件摆放的重排 | 属场景重排批,不与本卡混装(用户 2026-11-05 口径:代码批与素材批分两张卡) | 归那一批自己的卡 | +| 营地屏 golden 建锚 | 无改前基线可留底,新建锚不属缺陷修 | 不登记(顶栏观感走目检) | +| `main.gd:137` 的 boot 期 `start_overlay.visible = true` | 启动默认态,与三件无关 | 无 | + +**回归面(本批不改、但跑批必须仍绿)**:七条未改动动线——大门有断点按「继续上一段冒险」/大门按「取消」「否」/篝火「离开」「留下」/Esc 浮窗三项/五功能窗开合/进入存档与生涯页返回的开始层显隐/终局回菜单。其中前三条已被 `ui_click_probe.gd:679-723` 段覆盖(段止于「开场行三路径段」头注之前),本批在该段内追加断言,不新建平行段。 + +--- + +## 6. 关联文档同步面 + +实现批禁写 `design/` 与根规则文件;下表逐条给出「要改哪一节、改成什么、依据是什么」,实现批把命中项列进 `reports/iter<53>-反哺点-<日期>.md`,由用户在会话中裁决后归文档批落文。 + +| 文档 | 关联点 | 同步义务 | +|---|---|---| +| `design/gdd/ux/camp.md` v2.8 §顶栏 | 本批把「栏体定值」变成实装;若实现期样张/目检意见要求改 α、改右位内缩或改栏高,现行句即落后 | 反哺点:只在**实际改值**时列,附实测读数;未改值不列 | +| `design/art/art-bible.md` v3.43 §6.2 钥石余额行 | 同上(该行已把事实源指回 camp §顶栏,改值须两处同改) | 反哺点:与上一行同批列,禁单改一处 | +| `design/gdd/systems/表现与支撑/HUD框架.md` §提示与弹层·其三 | 「四处共用同一件同一序」实装后须与代码一致;若实现发现某消费方不遵此序(例如自拼按钮行的弹窗件),句与码冲突 | 反哺点:发现即列,附 `文件:行` | +| `design/engineering/architecture/architecture.md` 呈现层模块表 | 两处随本批失真:`:175` CampHudBand 行现写「顶栏 48px 全宽窄条:**钥石余额 badge 唯一呈现**」——加可见栏体后badge 不再是唯一呈现物;`:168` D1 HUD 行的视图单元接口若因本批改动而与「信号外发、setter 只读注入」不符亦须同步 | 反哺点:实现批禁写该文档;本批落地后把该行的实际形态(栏体色面+badge 右位)列进反哺点清单,附 `文件:行` 与实装读数 | +| `体验验证清单.md` | 顶栏观感属「需真人试玩才能下结论」项,需一行观测项 | 反哺点:实现批禁写该清单,由文档批落;卡内先在实现报告目检总清单登记 | +| `game/tests/README.md` 能力表 | 两行新增能力已由写卡段落表(第 8 节能力行列名即清单名) | 实现批禁写;批末只核「清单名=实际落地函数名」,名实不符=停卡退回写卡段 | +| `roadmap.md` 迭代总表 | 本卡尚未编入迭代 | 非实现批义务,归 iter-plan 段 | +| (非文档面·写卡期发现一)`tools/pipeline_gate.py:68` 判据出处行锚的机检式 `ANCHOR_RE` 字符类=`[A-Za-z0-9_-.\\/]`,不含中文段 | 本库现行设计文档多为中文名(`HUD框架.md`/`关卡节点地图.md`/`待办清单.md`),凡判据句落在这类文档即无法被机检认锚——本卡 R2/R3/R4/R8 因此同时挂一个 ASCII 路径锚(`camp.md`/`level-map.md`),中文出处改以「第 N 行」散文形写明 | **已登记**:待办清单「流水线闸认锚式不含中文名文档」条(工具代码/待执行),本批不动闸件 | +| (非文档面·写卡期发现二)`game/tests/unit/contracts/source_guard_test.gd:85-99` 列目录不递归 | 「V1d 层序落位单口」的扫描面漏掉 `views/` 与 `scenes/` 两个子目录(注释仍写「presentation 无子目录」,实为两个);视图层的插位不在该闸视野内。当前无漏网真违规(`views/ui_kit.gd:841` 属卡片内部重排) | **已登记**:待办清单「层序落位单口检查的扫描面不含子目录」条(工具代码/待执行);两目录是否纳入扫描面本条不预裁,启动时再裁 | + +--- + +## 7. 文件结构图 + +| 文件 | 职责 | 新增/改动 | 行为改动 | 验收行 | +|---|---|---|---|---| +| `game/presentation/main.gd` | 组合根:冒险启动与确认分派编排 | 改动(`_start_adventure()` 加一句;`_start_adventure_from_menu()` 删一句) | 行为改动 | R1、R2 | +| `game/presentation/views/confirm_dialog.gd` | 通用确认弹窗视图件 | 改动(三句 `add_child` 次序,**不动成员面**) | 行为改动 | R3、R4 | +| `game/presentation/views/ui_kit.gd` | 呈现层共享工厂与全站取色单点 | 改动(新增一枚带 α 派生常量 `CAMP_HUD_BAND`;**不新增方法**) | 行为改动(顶栏呈现的数据源) | R5、R6 | +| `game/presentation/views/camp_hud_band.gd` | 营地顶栏 HUD 件 | 改动(新建栏体矩形引用该常量+徽章落座改右对齐容器) | 行为改动 | R5、R6 | +| `game/probe/ui_click_probe.gd` | 真点探针:大门两分支段所在宿主 | 改动(同段追加断言) | 非行为改动(测试面) | R1、R2、R4、R7、R8 | +| `game/tests/system_test.gd` | 系统测试宿主 | 改动(新增 `_smoke_camp_hud_band()`,接进 `_initialize()`) | 非行为改动(测试面) | R5、R8 | +| `game/tests/unit/presentation/camp_scene_layout_test.gd` | 营地屏布局与顶栏禁区单元套 | 改动(新增 `_test_hud_band_contrast_over_bg()`) | 非行为改动(测试面) | R6 | +| `game/tests/unit/presentation/confirm_dialog_test.gd` | 确认弹窗按钮位次与语义单元套 | **新增** | 非行为改动(测试面) | R3 | +| `game/tests/README.md` | 能力清单 | 改动(两行,写卡段落) | 非行为改动 | — | + +结构图与第 12 节「落位与可复用性预检」互指:改动件四个、新增件仅一枚单元套;顶栏矩形为何不复用盘上现成那两份、也不抽新工厂,理由见第 12 节与第 15 节的留痕句。 + +--- + +## 8. 验证闸(改动—验收对照表) + +| 行 | 需求点(判据出处行锚) | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---|---| +| R1 | 件1 大门无断点按「是」后开始层不可见(`design/gdd/ux/camp.md:16`+`design/gdd/systems/冒险循环/关卡节点地图.md:78`) | `main.gd:211-212` 分支经 `main.gd:292` 新入口隐藏 | `ui_click_probe.gd:_ready`(大门无断点段追加「确认后开始层不可见」) | 探针 | 营地场景根大门命中区 → `ProbeUiUtil.push_click_at` 真点 → 弹窗「是」钮 `ProbeUiUtil.push_click(_confirm_btn)` 真点 | `main.ui.screens.start_overlay.visible` 取值(被测改动特有的可见性提交面) | 既有:营地大门两分支确认与断点续玩接线 | +| R2 | 件1 开局落在节点图选路停点(`design/gdd/ux/level-map.md:69`;停点句唯一出处=`design/gdd/systems/冒险循环/关卡节点地图.md` 第 78 行「关卡开始 → 呈现节点图选路停点」) | 同 R1 | `ui_click_probe.gd:_ready`(同段追加「关卡画面在位」) | 探针 | 同 R1 真点链(大门命中区真点 →「是」钮真点) | `run_scope.is_adventure` + `main.ui.modals.run.level_screen.visible`(停点呈现面,非终态推断) | 既有:营地大门两分支确认与断点续玩接线 | +| R3 | 件2 两出口与三出口形态的按钮位次=推进→次→维持(`design/gdd/ux/camp.md:16`;位次定值唯一出处=`design/gdd/systems/表现与支撑/HUD框架.md` 第 90 行「三条纪律·其三」) | `confirm_dialog.gd:31-39` 三句 `add_child` 次序镜像;**成员面与连接一律不动** | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order` | 单元 | 视图件对外入口 `ConfirmDialog.open(text, confirm_label, cancel_label)`/`open_three(text, confirm_label, secondary_label, cancel_label)`——即四消费方实际用的那两个公开方法(`main_overlay_flow.gd:72/76/82/129`) | 测试自己传入三档互异文案(如「推进」「次推」「维持」),从弹窗节点向下按类型定位那个横排容器(HBoxContainer),断言其 `get_children()` 的**子节点顺序**:双钮形态 child(0) 文案=「推进」且 **child(2)** 文案=「维持」——次钮在 `open()` 里只是 `visible=false`、不从子节点表移除(`confirm_dialog.gd:47`),双钮形态下子节点仍为 3 个,按下标断必须取 child(2)(写 child(1) 必红);三钮形态 child(0)/child(1)/child(2)=「推进」「次推」「维持」。另配非空前置:按钮行子节点数=3(防扫描面缩水静默绿)。按类型定位可成立已核:该弹窗树内只有这一个 `HBoxContainer`(`UiKit.modal_shell()` 产 VBox+HSeparator,`ui_kit.gd:509-525`;`UiKit.button()` 无子节点)。判据面=树的公开子节点序(同 `system_test.gd:81-89` 断言层序的先例形态),**不读任何私有成员**,避开 `architecture.md:168` 视图单元接口与 `:323` 禁直引实现内部 | 新增:确认弹窗按钮位次与语义同钉 | +| R4 | 件2 左位那颗真点下去就是推进(`design/gdd/ux/camp.md:16`;位次定值出处同 R3) | 同 R3 | `ui_click_probe.gd:_ready`(大门无断点段内,`push_click_at` 打按钮行左位中心坐标) | 探针 | 弹窗开态下按按钮行实际矩形算左位坐标真点 | `_confirm_consumer` 在真点前为 `"gate_new"`、真点后走开局分支(`run_scope.is_adventure` 翻转+`_confirm_consumer` 清零)——位次改动若错接,坐标命中的将是「否」,该面即红 | 新增:确认弹窗按钮位次与语义同钉 | +| R5 | 件3 顶栏栏体几何与徽章落座(`design/gdd/ux/camp.md:41`) | `camp_hud_band.gd:10-17` 新建栏体矩形引用新常量+徽章落座改右对齐容器;`ui_kit.gd` 加 `CAMP_HUD_BAND` | `system_test.gd:_smoke_camp_hud_band` | 集成 | 真实工程 boot 后经装配链 `main_ui_build.gd:120-121` → `camp_scene_view.gd:88-91` 落座;**须先开营地**(营地屏 boot 态为隐,`camp_scene_view.gd:51`),走 `main._camp_pres.open_camp()` 探针既有用法并 await 排版帧,再读 `main.ui.modals.camp.scene.hud_band`(`camp_scene_view.gd:49` 的公开句柄) | ①栏体色 `color.to_html(true) == "6e4526cc"`——即规格「`#6E4526` × α0.80」的字面编码;**禁写 `.a == 0.80`**(实测色道单精度使该比较为 false,见第 2 节实测句);②栏体矩形铺满:`size == Vector2(1280, 48)`;③`mouse_filter == MOUSE_FILTER_IGNORE`;④栏体是顶栏件的**第一个子节点**(子节点序=绘制序,色面必须先入才不盖徽章);⑤徽章全局矩形 `position.y == 0` 且 `end.x == 顶栏全局矩形 end.x − 12`(x 向缺判据则位次改错仍绿,故与顶格同批配齐)。前置量测=T3.0 读 `get_combined_minimum_size().y` 须为 48(描边若计入得 50 → 触第 10 节 L3 第 5 条) | 新增:营地顶栏栏体定值与落座 | +| R6 | 件3 栏体对落位带底色对比 ≥3:1(`design/gdd/ux/camp.md:41`+`design/art/art-bible.md:452`) | 同 R5 | `camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` | 单元 | 单元从 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口取带高,读现行背景贴图 `res://assets/art/scene/scene_camp_bg.png` 该带像素(`load(...)` 取纹理再 `get_image()`+`get_pixel`,先例=`terrain_tile_sets_test.gd:105-107` 的全像素扫描) | 规格侧算式核验(与 R5 分工:R5 核实装等值,本行核规格值本身达标)=测试内按 `camp.md:41` 写死 `#6E4526` 与 α0.80,与该带实测均色 alpha 合成后算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 正面效力自检配齐。**禁把视图件的 color 直接当预期值喂进算式**(那是 oracle 镜像,test-conventions §8.7 判红) | 新增:营地顶栏栏体定值与落座 | +| R7 | 回归·件1 未动的三条动线(`design/gdd/ux/camp.md:16`/`:22`) | 无改动(`main.gd:208`/`214`、`main_esc_flow.gd:46`) | `ui_click_probe.gd:_ready`(既有大门两分支段+篝火离场段现有断言,本批零改写) | 探针 | 既有真点链 | 既有绑定面原样:`is_adventure` 不翻转、营地场景根 `visible`、`_camp_data()["balance"]` 不变 | 既有:营地大门两分支确认与断点续玩接线 | +| R8 | 回归·件2 未触的路由与件数断言(`design/gdd/ux/camp.md:16`;位次与件数定值出处同 R3) | 无改动 | `ui_click_probe.gd:_ready`(次钮隐藏断言 `:694`)+ `system_test.gd:_smoke_boot_chain`(开始层三钮计数 `:195`) | 探针/集成 | 既有真点与 boot 链 | 既有绑定面:`_secondary_btn.visible` 为假(两出口形态)、开始层可见钮数 == 3 | 既有:营地大门两分支确认与断点续玩接线 | + +闸口径:本批改动含 `game/` 下非 `.md` 的行为载体,合回后远端 CI 走代码域全档;开发期按改完即测铁规以 `tools/run_all_gates.sh --code` 为选档入口(域划分唯一出处在脚本内,卡内不列闸件名单)。里程碑步出口跑一次所选档全套;波内修复只做出红件的最小单元最窄确认;新增测试先行孤立验证再并入全套。 + +**测试削弱申报**:允许改动 0 条既有断言。`ui_click_probe.gd:694`(次钮隐藏)与 `system_test.gd:195`(三钮计数)必须原样通过;本批位次改动若使二者失守=契约3 被违,停卡呈报,禁改判据求绿。**唯一允许的判据形态约束**:涉及色值与透明度的断言一律走 `to_html(true)` 或整体等值比较,禁浮点分量直等(第 2 节实测依据)。 + +**独立验收审计**:入口=`reports/iter<53>-验收审计-<日期>.md`;由未参与实现的只读会话或子代理执行,未过不得合回(test-conventions §8.7+iter 技能第 5 步)。验收档=中 ⇒ 审计深度按 §8.7 中档梯度。 + +--- + +## 9. 任务卡 + +### T1|开局路径的隐藏点收一处(≈0.25 工作日) + +取用:`main.gd:292 func _start_adventure() -> void`(既有签名,零参数零返回)。 + +- [ ] T1.1 实装:`game/presentation/main.gd:292` `_start_adventure()` 函数体首行加 `ui.screens.start_overlay.visible = false`(位置在该函数现有第一句 `ui.flow.close(...)` 之前)。 +- [ ] T1.2 实装:删除 `game/presentation/main.gd:194` 那句 `ui.screens.start_overlay.visible = false`(转调 T1.1 后它已无职责)。 +- [ ] T1.3 验证:`python tools/run_system_test.py` 全绿——核 `_smoke_boot_chain` 与 `_smoke_run_lifecycle` 的既有开始层断言(`system_test.gd:170-171`/`:253`/`:261`)未失守。 +- [ ] T1.4 验收:`ui_click_probe.gd` 大门无断点段(`:684-705` 区间内)追加两条断言并实跑——R1(确认后 `main.ui.screens.start_overlay.visible` 为假)、R2(`main.ui.modals.run.level_screen.visible` 为真,与既有 `is_adventure` 断言同批)。**帧口径**:段内既有 await 只 1 帧(`:703-704`),选路停点成形的先例是 3 帧(`screen_flow_probe.gd:185-193`,该段在 `await _frames(3)` 后断言「入口停点未落在关卡地图屏(level_screen 不可见)」——但它走 `_start_adventure_from_menu()` 函数直调,那条路径自带隐藏,所以从未抓到本缺陷)——追加断言前 await 到 3 帧,并把「停点成形所需最小帧数」实测值记进实现报告。落点=既有段内,不新建段。 +- [ ] T1.5 验证:`python tools/run_all_gates.sh --quick` 对该件做最窄确认(探针闸段),先红后绿记进实现报告。 + +产出(T2/T3 无关,供回归引用):`_start_adventure()` 成为开始层隐藏的唯一开局出口。 + +### T2|确认弹窗按钮位次镜像(≈0.5 工作日) + +取用:`confirm_dialog.gd:43 func open(text: String, confirm_label: String, cancel_label: String = "取消") -> void`;`confirm_dialog.gd:55 func open_three(text: String, confirm_label: String, secondary_label: String, cancel_label: String) -> void`。三颗钮的成员声明(`:14-16`)与连接属本卡禁动面,不取用。 + +- [ ] T2.1 实装:`game/presentation/views/confirm_dialog.gd:31-39` 三颗钮的 `add_child()` 次序整体镜像为 确认 → 次 → 取消。对象创建、`.pressed.connect(...)` 三句连接、成员声明(`:14-16`)、`open()`/`open_three()` 签名与文案参数一律不动(契约3)——**本卡不给测试开任何新成员或新公开出口**,位次判据走公开子节点序(依 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部)。 +- [ ] T2.2 验收(新增件):新建 `game/tests/unit/presentation/confirm_dialog_test.gd`,套件入口=`func _initialize() -> void`(视图件套形态先例=`camp_scene_layout_test.gd:12-13` 的 `call_deferred` 串法,**不是** `_init`),体内 `call_deferred` 两个私有函数——`_test_two_button_row_order()`:自造实例后调 `open("t", "推进", "维持")`,从该实例向下按类型定位那个 `HBoxContainer`,断言其 `get_children()` 的 child(0).text 为「推进」、**child(2).text** 为「维持」,且子节点数=3(次钮只置 `visible=false` 不移除——`confirm_dialog.gd:47`,双钮形态下维持钮在 child(2) 不是 child(1));`_test_three_button_row_order()`:调 `open_three("t", "推进", "次推", "维持")`,断言 child(0)/child(1)/child(2) 文案依次=「推进」「次推」「维持」。全程只读公开树面与 Button 的公开 `text`,不触任何私有成员。入册=在 `game/tests/run_all.gd:166` 起的硬编码清单里追加一行 `{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}` 并行尾注一句本套件职责(清单条目形态照同目录既有行)。 +- [ ] T2.3 验收:`ui_click_probe.gd` 大门无断点段内追加 R4。**前置重开**:`:702` 那一次真点已把 `gate_new` 消费掉(`main.gd:215` 置空),故本步须先 `main._camp_pres.open_camp()` → `ProbeUiUtil.push_click_at(gate 命中区中心)` 重出弹窗,再取按钮行左位——坐标=自弹窗节点向下按类型定位到的 `HBoxContainer` 的 child(0) 全局矩形中心,经 `ProbeUiUtil.push_click_at` 真点;断言真点前 `_confirm_consumer == "gate_new"`、真点后 `run_scope.is_adventure` 为真且 `_confirm_consumer` 清零。 +- [ ] T2.4 验证:孤立先跑 T2.2 新套(`… --script res://tests/run_all.gd` 单套件口径),绿后并入全套;四处消费方动线由 `ui_click_probe.gd` 既有段与 `run_all` 单元套确认未失守(R8)。 + +产出:位次成为规格钉死的呈现定值,推进钮恒为该按钮行的 child(0)。 + +### T3|营地顶栏可见栏体(≈0.75 工作日) + +取用:`UiKit.CAMP_HEADER_BAND`(`ui_kit.gd:195`,色值单点);`UiKit.keystone_badge(text: String) -> KeystoneBadge`(`ui_kit.gd:655`);`CampHudBand._init()`(`camp_hud_band.gd:10`);`Control.PRESET_FULL_RECT`/`Control.MOUSE_FILTER_IGNORE`(引擎枚举,栏体铺满与不吃命中的既有形态见 `camp_hud_band.gd:11-13`)。 + +- [ ] T3.0 前置读数:**入树之后**再量——`KeystoneBadge` 是 PanelContainer(`ui_kit.gd:634-656`),最小尺寸由容器排版产出,刚 `new()` 未入树时读数不可信。落点=在系统测试宿主里经 `main.ui.modals.camp.scene.hud_band` 链取**已在树的现成徽章实例**,先开营地并 `await` 排版帧(帧数口径同 T1.4),再读 `get_combined_minimum_size().y`(`ui_kit.gd:640` 的 `panel_flat(...Vector4(14,8,14,8))` 推得 32+8+8=48,但 `border_w=1` 是否计入最小尺寸未证实——**这是推导,不是实测量**)。读数=48 → 按 T3.2 顶格落座;读数≠48(如 50)→ 停步走第 10 节 L3 第 5 条呈报,禁自行改栏高或改徽章内边距。读数与取数时机一并写进实现报告。 +- [ ] T3.1 实装(**Q1 已裁·2026-11-05 用户裁「按放在公共区域的这一层」**):先在 `game/presentation/views/ui_kit.gd` 的色板段(`CAMP_HEADER_BAND` 定义在 `:195`,同文件带 α 先例见 `:189`/`:190`)新增 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`,注记沿 `HP_BAR_TRACK`(`:200`)的「视图原裸值收口单点——非色板新增」说法。再在 `game/presentation/views/camp_hud_band.gd` 的 `_init()` 内、挂徽章**之前**新建栏体矩形:`ColorRect.new()` + `color = UiKit.CAMP_HUD_BAND` + `set_anchors_preset(Control.PRESET_FULL_RECT)` + `mouse_filter = Control.MOUSE_FILTER_IGNORE`(契约5)+ `add_child`——顶栏件自建是该目录现行形态(8 个文件 15 处先例,含本件自己的徽章),**不复用 `ui_assembler.gd:224` 的 `overlay_bg()`**(它的头注 `:2-3` 把服务对象写成「供组合根接线」,且外部调用者零;视图引它会新开一条 `views → ui_assembler` 路径),**也不抽新工厂**(意图三分,见第 12 节留痕句)。子节点序即绘制序,故栏体必须先入、徽章后入。 +- [ ] T3.2 实装(**Q2 已裁乙·2026-11-05 用户裁**):徽章落座改为「栏体内加一层右对齐容器」——新增 `HBoxContainer` 铺满栏体(`set_anchors_preset(Control.PRESET_FULL_RECT)` 之后置 `offset_right = -12.0`)、`alignment = BoxContainer.ALIGNMENT_END`、`mouse_filter = Control.MOUSE_FILTER_IGNORE`,徽章作该容器子件、由容器摆位,余额位数增宽时自然向左展;替换 `camp_hud_band.gd:15-16` 现值(`PRESET_CENTER_RIGHT` + `position=(-160, 8)`,系无栏体时代把 x 写死的代拍板)。容器右对齐先例=`confirm_dialog.gd:27-29`(HBoxContainer+alignment 摆位,非手算偏移)。内缩定值 12px 不变(白名单第 3 条)。 +- [ ] T3.3 验收:`game/tests/system_test.gd` 新增 `_smoke_camp_hud_band()` 并接进 `_initialize()`,实现 R5 的五项判据(色值走 `to_html(true) == "6e4526cc"` 或整体等值比较,**禁浮点分量直等**;`size == Vector2(1280, 48)`;`mouse_filter == MOUSE_FILTER_IGNORE`;栏体是顶栏件的第一个子节点;徽章 `position.y == 0` 且 `end.x == 顶栏 end.x − 12`)。 +- [ ] T3.4 验收:`game/tests/unit/presentation/camp_scene_layout_test.gd` 新增 `_test_hud_band_contrast_over_bg()` 实现 R6:读 `hud_band_h` 与背景贴图该带**全像素**均色(取样法照 `terrain_tile_sets_test.gd:105-107` 的双层全扫,非步长抽样),规格值 `#6E4526`/α0.80 按 `camp.md:41` 在测试内写死后合成算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 配齐。 +- [ ] T3.5 验证:新增两测先行孤立跑绿,再并入 `--code` 全套;营地屏截图面由 `render_gate` 的七屏段随批产出,作 R5/R6 的对照取证。 +- [ ] T3.6 目检登记:营地顶栏观感(「成一条可辨窄带、徽章落在带内」)写进实现报告实机目检总清单,登记即完成义务。 + +产出:`CampHudBand` 成为顶栏唯一呈现件(栏体色面+右对齐容器+徽章),顶栏色值单点=新增的 `UiKit.CAMP_HUD_BAND`(其三分量继承 `CAMP_HEADER_BAND`,α 只此一处)。 + +### T4|产出物落位卡(≈0.5 工作日,末卡) + +- [ ] T4.1 代码域全档全绿后、合并前写 `reports/iter<53>-实现报告-<日期>.md`:各卡状态+实机目检总清单(含 T3.6)+遗留读数义务+实现决定记录(超白名单的微决策逐条登记)。 +- [ ] T4.2 写 `reports/iter<53>-反哺点-<日期>.md`:第 6 节每条命中项都要有落点(哪一节、改成什么、依据什么);无命中也要写「本批零反哺点」并附核过哪几处。 +- [ ] T4.3 核对 `game/tests/README.md` 两行新增能力名与实际落地函数名逐字一致(名实不符=停卡退回写卡段)。 +- [ ] T4.4 呈报合回清单,等用户发令再走收口(冻结→件核→并 master→重塑→删区)。 + +**估时合计 ≈1.75–2.0 个工作日**。切分预案:T3 若因取样件超 0.25 日未完,先合 T1+T2(两件互不依赖),T3 另起一批——第 1 节三件本无耦合。 + +--- + +## 10. 异常分级 + +- **L1 自救**:探针坐标算错导致 R4 打空——重算按钮行全局矩形中心,禁改为信号直发(段内既有 `ui_click_probe.gd:696` 的 `cancelled.emit()` 是取消路径的信号注入先例,注释自陈「真实信号——cancelled 钮同源」;本批 R4 要钉的正是**位次**,直发不经位次,等于没测——依据=test-conventions §8.3 真实入口收窄条,契约7 同源)。 +- **L2 记录继续**:R6 均色取样读数与卡内实测 `#DEEAE7` 有 ≤3/255 的取样步长差——按实测值断言、卡内读数差写进实现报告,不改阈值。 +- **L3 停机呈报(写报告等用户)**: + 1. boot 后 `main.ui.modals.camp.scene.hud_band` 取不到实例(装配链与第 3.3 条现状不符)=卡片现状事实失效,停卡退写卡段; + 2. R4 坐标真点无法区分左位钮与相邻钮(按钮行 `alignment=CENTER` 下实际矩形与规格不符)=位次与语义绑不上观测面,触 test-conventions §8.4「判据绑不上=停卡呈报」出口; + 3. 位次改动使 `system_test.gd:195` 或 `ui_click_probe.gd:694`(次钮隐藏断言)失守=契约3 被违,禁改判据求绿; + 4. 机检预算互斥限**本批触碰的 presentation 面文件**:`tools/gdstyle_gate.py:45` 的扫描面=`game/core`/`game/data`/`game/presentation`,脚本头注(`:8`)明写排除 probe/sim/tests——故新增单元套**不在** S13 射程,该条只对 `main.gd`/`confirm_dialog.gd`/`camp_hud_band.gd` 生效。若这三个件的 S13 键与第 13 节预算数字冲突且无豁免通道=上游缺陷,停卡退回写卡段,禁自造第三条路(本技能第 13/14 节)。 + 5. T3.0 量得徽章 `get_combined_minimum_size().y` ≠ 48(描边宽 1px 若计入最小尺寸则推得 50)=「顶格落座」在 48px 栏内放不下,第 2 节与 `camp.md:41` 的推导句失效,停步呈报,禁自行改栏高、改徽章内边距或放宽 R5 判据。 + +--- + +## 11. 代拍板白名单(超清单必进实现报告「实现决定记录」并呈报) + +1. 断言消息尾缀文案(照 `〔营地图批 V3〕` 形态自拟)。 +2. 新单元套件的套件注册名与 `run_all.gd` 册内落点序。 +3. **徽章水平内缩 12px**:`camp.md` v2.8 只钉「右位顶格」未钉内缩值,取 UiKit 钮间距同档 12(先例=`confirm_dialog.gd:28` `separation=12`)。 +4. R6 取样密度——照 `terrain_tile_sets_test.gd:105-107` 的**双层全像素扫描**(该件不是步长抽样,卡内早先写的「每 4 行×每 8 列」口径作废)。 +5. `_smoke_camp_hud_band()` 在 `_initialize()` 内的插入位(不改变既有段序)。 +6. 新色值常量的命名(卡内暂定 `CAMP_HUD_BAND`)——只要它是「一枚带 α 的派生常量、不加方法、不进色板五件表」这个形态已定,名字可由实现定;若实现改成加方法或加语义色,触契约4 与第 13 节,属 L3 呈报。 + +不在白名单:α 值、栏底色、栏高、位次序、隐藏点数量与位置——这些是规格钉死项,改动一律 L3 呈报。 + +--- + +## 12. 落位与可复用性预检(与第 7 节结构图互指) + +| 新增/改动件 | 复用面(盘上已有什么) | 唯一面 | 分层与交互面 | +|---|---|---|---| +| 栏体矩形(T3.1,Q1 已裁) | 盘上已有两份同形实现:`ui_assembler.gd:224-230` `overlay_bg()`(**外部调用者零**,仅该文件自身 `:100`/`:214` 用)与 `ui_kit.gd:486-491` `_modal_dim()`(私有,仅 `:511` 一处用)。本批两份都不复用、也不抽新工厂,按 `views/` 现行形态自建(该目录直接 `ColorRect.new()` 有 8 个文件 15 处先例,含本件自己的徽章)。不引装配器的依据:它头注 `:2-3` 把服务对象写成「返回句柄供组合根接线」「接线拓扑唯一归组合根」,现网引用方全在组合根与屏壳侧(`main_ui_build.gd:7`、`scenes/adventure_screen.gd:21`、`scenes/menu_screen.gd:15`),视图引它等于新开一条 `views → ui_assembler` 路径。**不抽工厂的留痕句见本节末** | 顶栏呈现件唯一 = `CampHudBand`;色值单点 = 新增的 `UiKit.CAMP_HUD_BAND`(`Color(CAMP_HEADER_BAND, 0.80)`,三分量继承在册 token、α 只此一处),视图与断言都引用它,禁在别处出现第二组「同一色+同一 α」 | 呈现层内;读数仍经 `camp_data()` 投影单口不变(`camp_hud_band.gd:26-27`),本批零新增跨层引用路径 | +| 开始层隐藏(T1.1) | **复用** 既有语义句本身——`main.gd:194`/`:246` 就是同一条责任,本卡把它收进被调方 | 开局路径隐藏点唯一(`_start_adventure()`),续玩路径唯一(`_resume_from_menu()`);其余 10 处开层自管点不在本契约射程(契约1 已收窄) | 组合根内;不触 Core,不新增信号 | +| 按钮位次(T2.1) | 无新增件——只镜像既有三句的次序。**本卡不新增成员、不新增公开出口**:位次判据走公开子节点序(R3),语义判据走坐标真点(R4) | 位次定值只写在视图件 `_init()` 一处,四处消费方零副本 | 呈现层;信号对象路由不动;依 `architecture.md:168`「视图单元接口=信号外发+setter 只读注入」与 `:323`「禁直引呈现层实现内部」,不为测试开成员面(该写法曾在本卡出现过,已撤回,登记在第 16 节轮次 2) | +| 徽章落座(T3.2) | 已有做同样事的东西:`HBoxContainer` + `alignment` 摆位(`confirm_dialog.gd:27-29`)——**Q2 已裁乙(2026-11-05 用户裁)**,采容器右对齐形态;另两案(锚点生长、`size_updated` 回调)作淘汰留档见第 15 节 | 内缩值 12px 只在落座一处出现一次,禁在断言里另写第二套推导 | 呈现层内 | +| `confirm_dialog_test.gd`(T2.2 新增) | 盘上无 ConfirmDialog 专属单元套(grep 命中面=`system_test.gd`/探针的宿主级引用与 `README.md:159` 能力行);同目录 `camp_scene_layout_test.gd:12-13` 是可参照的套件形态(入口 `_initialize()`+`call_deferred`),不是可复用的承载 | 位次判据只此一处;R4 探针判据与之互补(一个核序、一个核语义) | 测试层;经 `ConfirmDialog.open()/open_three()` 公开入口进出,断言只读公开子节点序与 Button 的公开 `text`——「读私有成员」的写法曾在 R3 出现,已按 `architecture.md:323` 撤回 | +| `system_test.gd:_smoke_camp_hud_band`/`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg`(T3.3/T3.4) | **扩展**既有套件,不新建宿主;取样法复用 `terrain_tile_sets_test.gd:105-107` 的双层全扫+`get_pixel` 路径 | 几何判据落系统测试、对比判据落布局单元套——两处职责已由套件名界定,禁第三处重复断言 | 测试层 | + +**同形不同意图的留痕句(本卡有意接受这份重复)**:铺满一个控件的纯色矩形这件事,盘上已有两份实现、四种用途——亮底面板(`ui_assembler.gd:100` 用 `BG_BRIGHT`)、模态压暗遮罩(`:214` 用黑 0.6)、tooltip 遮罩底(`ui_kit.gd:486`),与本批的常驻 HUD 条带。三次法则的触发条件是「同一功能第三次出现」,此处是同一**形状**第四次出现、同一**意图**第一次出现,故不抽公共工厂;把它抽成一个件得到的只会是"随便铺个矩形",属形状公因数而非概念。若日后出现第二个**同意图**件(又一个常驻 HUD 条带),再行提取。该取舍按 Fowler 技术债四象限属「有意且审慎」一格,凭据即本句+实现报告的实现决定记录。(本句由用户 2026-11-05 的架构取舍口径定:体量未变、矛盾未现,不为未到来的问题预先设计。) + +--- + +## 13. 实现规范预算(与第 7 节结构图互指) + +**新件三查**——本批新增文件仅 `game/tests/unit/presentation/confirm_dialog_test.gd`: + +- **三查对本件不适用**:`tools/gdstyle_gate.py:45` 扫描面=`game/core`/`game/data`/`game/presentation`,脚本头注 `:8` 明写「probe/sim/tests 测量面」排除——测试面文件不进 S13 读数,无键可对账。 +- 尺寸预期(照套件形态先例自持,不作机检项):套件入口 1 个(`func _initialize() -> void`,**非** `_init`——先例=`camp_scene_layout_test.gd:12-13`)+私有 `_test_*` 2 个+类变量 0–1。 +- 迁移函数对账:无迁入(该件全新增,`tools/gdstyle_baseline.txt` 无历史键,跑批后若出超默认新键须申报) + +**存量触面一查**——S13 键较登记值只降不升: + +| 文件 | 基线登记(`tools/gdstyle_baseline.txt`) | 本批预期 | +|---|---|---| +| `game/presentation/main.gd` | 第 11 行 `quality/max-public-methods = 32` | 不动公开方法面(函数体内加一句+另一函数删一句)→ 读数 32 持平 ✓ | +| `game/presentation/views/ui_kit.gd` | 第 12 行 `quality/max-public-methods = 23` | 本批**只加一枚 `const`、不加方法** → 读数 23 持平 ✓。色板快照面也不动:`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 常量从不进快照(同文件 `TOAST_BG`/`TIP_BG` 即此形态) | +| `game/presentation/views/confirm_dialog.gd` | 无登记键(在默认阈值内) | 仅语句重排,分支与返回数不变 ✓ | +| `game/presentation/views/camp_hud_band.gd` | 无登记键 | `_init()` 加栏体与容器两处构造、无分支 → 分支数不变 ✓ | +| `game/probe/ui_click_probe.gd` | 无登记键(探针大文件另有基线口径) | 同段追加断言,函数不拆;若单函数分支数超默认,按 L3 第 4 条呈报而非自拆 | +| `game/tests/system_test.gd` | 无登记键 | 新增一个私有函数,公开方法面不变 ✓ | + +**指标例外申报**:无。本批不申请任何超默认项。 + +--- + +## 14. 拆分边界纪律 + +本卡非拆分/收敛/重构类:**不适用**——零拆出单元(无新私有函数族、无新件从存量文件迁出、无段件/状态件),唯一新增是一个视图单元套件,其职责语句=「钉确认弹窗的按钮位次与语义绑定」,单一变化原因=位次定值变更(第 12 节复用面同载)。code-conventions §1.1 拆分判据与本节互指,实现期若发现需要拆 `camp_hud_band.gd` 或 `main.gd` 才落得下,属卡片未预见的结构动作=L3 呈报,禁为凑绿硬拆。 + +--- + +## 15. 待裁点(复核产生,两条都合规、代价不同——设计权归用户) + +独立复核(未参与撰写的只读子代理)把本卡打回一次,四条闭合里三条由写卡段按权威文档自决落卡,第 4 条拆成两案呈用户裁决。**两案现已裁尽,本卡零待裁点,开工门=U0(用户批卡)。** 以下留档=各案备选、代价与裁决,供复核与编排段比对,不再重开。 + +### Q1|栏体这一面从哪来——**已裁(2026-11-05 用户裁「按放在公共区域的这一层」)** + +事实底座:`UiAssembler.overlay_bg(host, color)`(`game/presentation/ui_assembler.gd:224-230`)确实做了「ColorRect + PRESET_FULL_RECT + add_child + 返回句柄」这四步,语义就是「铺满宿主的色面」。但该文件 `extends RefCounted`、**无 `class_name`**,现网一律 `const UiAssembler := preload("res://presentation/ui_assembler.gd")` 引用(`main_ui_build.gd:7`、`scenes/adventure_screen.gd:21`、`scenes/menu_screen.gd:15`),**引用方全在组合根与屏壳侧,`views/` 目录零先例**;而 `views/` 内直接 `ColorRect.new()` 有先例(`views/battle_end_overlay.gd:261`/`:289`/`:314`)。 + +| 案 | 做法 | 代价 | +|---|---|---| +| 甲 | `camp_hud_band.gd` 加 `const UiAssembler := preload(...)`,调 `overlay_bg(self, Color(...))` | 复用现成件、零重复实现;但给 `views/` 开一条此前零先例的装配器引用路径(该文件头注自陈「接线拓扑唯一归组合根——本类零信号连接」,被视图件引用属越界用法) | +| 乙 | 视图内 `var band := ColorRect.new()` 四行自铺 | 与 `views/` 既有形态同构、零新路径;代价是重复装配器已有的四行(第 12 节要申报「为什么不能复用那一个」) | + +呈裁时并列三案(甲=视图引装配器、乙=视图内自建矩形、丙=提到公共件层);裁决过程中又查实一条规范原文——`ui_kit.gd:2` 文件头「全站取色/描边唯一出口——各视图禁内联 Color8/StyleBoxFlat 字面量,换皮只改本文件」,它把"色值放哪"与"矩形谁建"这两件事分开了。 + +> **裁决(2026-11-05 用户裁「按放在公共区域的这一层」)**:取**丙的取值面+乙的动作面**——色值与 α 做成公共件里的一枚派生常量 `UiKit.CAMP_HUD_BAND`(已实测:`Color(CAMP_HEADER_BAND, 0.80)` 编译通过,`to_html(true)`=`6e4526cc`,三分量与母 token 逐一相等),矩形由顶栏件自建。**甲淘汰**(引入一条零先例且无机器检查拦截的跨文件引用);**丙的"抽公共工厂"那一半不做**,理由与留痕句见第 12 节末。已落 T3.1、第 7 节结构图、第 12 节、契约4、第 13 节。**解释面报备**:轮次 2 复核指出裁决句字面只写「放在公共区域的这一层」,未逐字覆盖「矩形谁建」这条轴。写卡段按"该回话针对的是当轮给出的整条推荐(色值进公共件+矩形留在视图自建)"处置,故两半同批落卡;用户若不认这一读法,只需回一句,重开范围仅止于顶栏矩形的建造方,不动色值落点与其他结论。 + +### Q2|徽章「右位顶格+内缩 12px」用什么机制随位数自适应——**已裁(2026-11-05 用户裁乙·容器右对齐)** + +事实底座:徽章文案是 `"钥石 %d" % v`(`camp_hud_band.gd:22`,格式串单点在 `UiKit` badge 工厂),**宽度随余额位数变**;现值 `position = Vector2(-160, 8)`(`camp_hud_band.gd:16`)把 x 写死——R5 要断言「徽章右缘 == 栏右缘 − 12」,写死偏移在位数变化时必然失位(现值 -160 只对当时那个位数读数成立)。 + +| 案 | 做法 | 代价 | +|---|---|---| +| 甲 | 锚点右钉 + `grow_horizontal = GROW_DIRECTION_BEGIN` + `offset_right = -12`、`offset_top = 0` | 纯声明式、无回调;依赖 Control 生长语义(须开工首步实测读数确认,Godot 4.7 锚点+offset 与 custom_minimum_size 的相互作用我不凭记忆断言) | +| 乙 | 栏体内套一个右对齐 `HBoxContainer`(`alignment = END`),徽章作子件由容器摆位 | 与 `UiKit` 既有容器用法同构(弹窗钮行 `confirm_dialog.gd:27-29` 就是 `HBoxContainer` + `alignment`);代价是多一层容器节点,栏体色面与容器的父子关系要重排 | +| 丙 | 保持现锚点,接徽章 `size_updated` 信号在回调里重算 x | 改动面最小;代价是多一条运行期回调路径(与「显式触发才变」的顶栏刷新纪律并存,需在卡内申报新观测路径) | + +写卡推荐=**乙**,理由=仓内已有同类摆位先例(容器 `alignment` 摆位,非手算偏移),且 R5 的 x 向判据在容器形态下读数最直白。甲依赖我不肯凭记忆断言的引擎语义(AGENTS.md:不确定的 API 一律核实后再用,核实前不作推荐)、丙新增运行期路径。 + +> **裁决(2026-11-05 用户裁·乙 容器右对齐)**:已落 T3.2 与第 12 节,Q2 清零。 + +**Q1/Q2 与第三条(闸件缺口是否登记)都已裁完并落卡,本卡零待裁点,开工门=U0。** + +### 其余复核发现的处置(已落卡,非待裁) + +自判与补写两类共 15 条已按复核者自己落卡的规矩改进行内:契约1 收窄措辞、`_start_adventure()` 函数体行号订正、失败分支终态申报、帧口径、R6 输入来源改读实件、R8 行号与能力行拆正、结构图验收行补 R7/R8、悬空「行为表」引用、L1 引用行号、白名单第 4 条取样口径、`_initialize()` 入口名、新件三查对 tests 不适用、完成定义行八行、`main.gd:137` 与其余开层点不受契约1 约束的明示。(轮次 1 对 R3 给的「升成员供测试直取」处置已在轮次 2 撤回——它抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部,改判为公开子节点序判据。) + +两条闸件面发现**均已登记为工程改动待办**(用户 2026-11-05 口径:登记后本批不管):认锚式字符类不含中文段(`tools/pipeline_gate.py:68`)、层序落位单口的扫描面不含子目录(`game/tests/unit/contracts/source_guard_test.gd:85-99`)。两条都在待办清单在册(改动面=工具代码/状态=待执行),本批不动闸件。 + +复核未采信两条:复核者称 `ui_click_probe.gd:679` 是断点写盘句(实为段头注,断点注入在 `:706` 起);称 art-bible:452 宣称的五 token 机检「查无该件」(`tools/palette_check.py:22` 的 `ui_tokens.png` 就是 ui_kit 全量 Color8 token 快照基线,机制存在,只是不逐名比对该表五行——已写进契约4 依据,未采纳其结论)。 + +--- + +## 16. 复核记录 + +复核者身份=未参与本卡撰写的只读子代理(Agent 工具起的独立会话,禁写文件、禁跑闸),形态=只读子代理。轮次 1,发现 22 条,四类去向逐条登记: + +| 轮次 | 复核者身份 | 发现(一句话) | 去向 | 处置结果 | +|---|---|---|---|---| +| 1 | 只读子代理·未参与撰写 | 契约1「隐藏点只许两处」按字面过宽——全库开始层显隐共 12 处 | 退回上游(复核者判)→ 落卡改自判 | 契约1 收窄为「限开局/续玩两条路径」,12 处逐条列名(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`)。复核者的「退回上游」定名不采纳:这是卡内条款措辞过宽,非规范冲突,写卡段可自改 | +| 1 | 同上 | `_start_adventure()` 函数体行号错(写 311,实止 308) | 自判 | §3.1 改 `292-308` 并注明 `:311` 是下一函数声明行 | +| 1 | 同上 | T1.1 落笔后启动失败分支的开始层终态由可见变隐藏,卡未申报 | 补写 | §3.1 该行补终态申报(致命屏自接管,无菜单可用) | +| 1 | 同上 | 大门段 await 只 1 帧,选路停点成形先例是 3 帧 | 补写 | T1.4 补帧口径+最小帧数实测记进实现报告;引 `screen_flow_probe.gd:185-193` | +| 1 | 同上 | `btn_row` 是 `_init()` 局部变量,R3「逐位相等」写不出来 | 补写(阻塞) | T2.1 同批把 `btn_row` 升为成员 `_btn_row`;R3 绑定面按 test-conventions §8.2 受控例外申报理由 | +| 1 | 同上 | §12 自称「不直取私有」,R3/R4 实际直取私有成员 | 自判 | §12 该格与 R3 改为受控例外口径(位次无公开可观测面可钉),不再谎称未直取 | +| 1 | 同上 | `:702` 真点已消耗 `gate_new`,R4 需重开营地+大门才有左位可点 | 补写 | T2.3 补「前置重开」步与坐标取法 | +| 1 | 同上 | L1 引用的 `ui_click_probe.gd:679` 不是旧口径注释;段内直发面在 `:696`;「emit 不算真实入口」属 §8.3 而非契约7 | 自判+**部分不采信** | L1 改引 `:696` 的 `cancelled.emit()` 并注依据=test-conventions §8.3。**但复核者称 `:679` 是断点写盘句,经我回读不成立**——`:679` 确为段头注「大门两分支断言段」,断点注入起 `:706`;另 R8 原引 `:696` 亦错,次钮隐藏断言实为 `:694` | +| 1 | 同上 | `ui_assembler.gd` 无 `class_name`,现网一律 preload,`views/` 零先例;卡内 `UiAssembler.overlay_bg(...)` 写法不成立 | 待裁 | 第 15 节 Q1 全案并列(复用装配器/视图内自建),含两案代价与写卡推荐(乙);T3.1 改为「待 Q1 裁决后落」 | +| 1 | 同上 | 徽章自高 48 系推导,`panel_flat` 带 `border_w=1`,描边若计入则 50、R5 顶格判据即红 | 补写(复核者自标「这是推测,未跑批」) | T3.0 新增前置读数步(量 `get_combined_minimum_size().y`);≠48 走 L3 第 5 条;§3.3 与 R5 的推导句标为未实测 | +| 1 | 同上 | 12px 内缩无可达机制(徽章宽随位数变,现值写死 x 偏移);R5 只断 y 向,x 写错仍绿 | 待裁+补写 | 第 15 节 Q2 三案并列(锚点生长/容器右对齐/size_updated 回调);T3.2 改「待 Q2 裁决后落」;R5 已补 x 向判据(`end.x == 栏 end.x − 12`) | +| 1 | 同上 | R6 若把实现的 `Color(UiKit.CAMP_HEADER_BAND, 0.80)` 当输入=oracle 镜像 | 自判 | R6 改为规格侧算式核验(测试内按 `camp.md:41` 写死 `#6E4526`/α0.80),并明写「禁把视图件 color 喂进算式」,依据=test-conventions §8.7 | +| 1 | 同上 | §11.4 称 4×8 步长「沿 `terrain_tile_sets_test` 口径」错,该件是全像素扫描 | 自判 | 白名单第 4 条改全像素双层扫描,并声明卡内早先步长口径作废 | +| 1 | 同上 | 数学校核:全扫与抽样均色=RGB(223,235,232),与卡内 `#DEEAE7` 差 ≤1/255;叠后对比 4.29:1(卡 4.27)、徽章对天空 1.30:1(卡 1.29) | 自判(读数采纳,容差不改) | 落位带前提经其独立核对(背景层 `Rect2(0,0,1280,800)`、围墙 y=261、大门 y=250 → 0–48 带只含背景);差值落 L2 容差内,不放宽阈值 | +| 1 | 同上 | §1 完成定义与 §5 写「七行」,对照表实为 R1–R8 | 自判 | 完成定义改「八行(R1–R8)」 | +| 1 | 同上 | §5 引「第 1 节行为表」,本卡无该表(悬空引用) | 自判 | 改为「七条未改动动线」自足表述 | +| 1 | 同上 | §7 结构图 `ui_click_probe`/`system_test` 的验收行列缺 R7/R8 | 补写 | 结构图两行补齐 R1、R2、R4、R7、R8 / R5、R8 | +| 1 | 同上 | gdstyle 只扫 core/data/presentation,tests 不在面 → §13 新件三查与 L3 第 4 条对新件无触发路径;套件入口是 `_initialize()` 非 `_init` | 自判 | §13 三查改「对本件不适用」并给依据(`tools/gdstyle_gate.py:45`+头注 `:8`);L3 第 4 条限定为 presentation 面三件;T2.2 入口名改 `_initialize()` 并附先例行号 | +| 1 | 同上 | §13 存量一查核过无误(基线 32/23,`main.gd` 公开方法 31+1 静态=32 持平成立) | 自判(确认类) | 无需改动,留档为已核项 | +| 1 | 同上 | 契约4 依据「加枚即触五 token 机检表」查无该件 | 自判+**不采信其结论** | 复核者称机检不存在,经我回读:`tools/palette_check.py:22` 的 `ui_tokens.png`=ui_kit 全量 Color8 token 快照基线,机制在——只是不逐名比对该表五行。契约4 依据改写成这个准确口径,仍禁新增 token | +| 1 | 同上 | `run_all.gd` 册是硬编码清单,T2.2「接进套件册」不够具体 | 补写 | T2.2 写死条目形态 `{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}` +行尾注,并指 `run_all.gd:166` 起的清单 | +| 1 | 同上 | 总判:按现状不能进实现段,缺四块闭合 | 退回上游(部分) | 四块里①②④已自决落卡;③的 `UiAssembler` 形态与 12px 机制转 Q1/Q2 待裁——**本卡现为「待裁未清零」状态,不进实现段、不落 commit、状态不推「已写卡」**。另其归入的机检缺口(`pipeline_gate.py:68` `ANCHOR_RE` 不含中文段)已列 §6 末行,去向=用户发令登记 | + +**写卡段对复核结论的两处不采信**(复核者也是读代码,不是跑批):`:679` 段头注的定性、五 token 机检「查无该件」——两处均经我逐行回读推翻,依据写在对应处置格。 + +**复核报告落盘**:`reports/复核-营地交互修复批-2026-11-05.md`(轮次 1=22 条,轮次 2=5 条,同一文件续段登记)。 + +## 轮次 2(Q1/Q2 裁决落卡后的独立复核) + +复核者身份=第二个未参与撰写的只读子代理(与轮次 1 不同一会话),任务书限定只查五处并须给 `文件:行号` 依据。 + +| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 | +|---|---|---|---|---| +| 2 | 只读子代理·未参与撰写 | **硬伤**:双钮形态下断言「child(1)=维持」必红——次钮在 `open()` 里只置 `visible=false`、不从子节点表移除 | 补写 | R3 与 T2.2 改判 child(2),并配「按钮行子节点数=3」非空前置;依据 `confirm_dialog.gd:47` | +| 2 | 同上 | R5 缺前置:营地屏 boot 态为隐,未开营地读不到尺寸 | 补写 | R5 真实入口补「先 `open_camp()` + await 排版帧」;依据 `camp_scene_view.gd:51` | +| 2 | 同上 | T3.0 未写取数时机:PanelContainer 的最小尺寸在入树排版后才可信,刚 `new()` 读数不可信 | 补写 | T3.0 改为「入树后经公开句柄读在树的现成徽章实例,await 排版帧再读」;依据 `ui_kit.gd:634-656` | +| 2 | 同上 | 确认类(无发现):八处行号全对(`main.gd:211-212`/`:223-224`/`:292-308`/`:194`/`:246`、`ui_kit.gd:640` 带 `border_w=1`、`ui_assembler.gd:100`/`:214`/`:224-230`、`ui_kit.gd:189/190/200`);`to_html(true)` 追加 α 两位、期望串 `6e4526cc` 对;尺寸链 1280×48 可达(CampScreen 满铺+`_hud_slot` 全宽,父链无容器改写);「按类型找第一个 HBoxContainer」不会找错(该树只有一个) | 自判(确认留档) | 无需改卡,核法记档:逐行打开比对+仓内既有用法 `standee_present_values_test.gd:36`(`to_html(false)` 六无 #) | +| 2 | 同上 | Q1 的裁决句「按放在公共区域的这一层」字面只覆盖色值落点;「矩形由顶栏件自建、不抽公共工厂」是写卡段对裁决的展开,该轴轮次 1 原属甲/乙案内容 | 待裁(复核者判)→ **按自判落并报备** | 依据对话原话序列:写卡段呈案时那句推荐是「色值进公共件、矩形留在顶栏件自建」(两半同批给出),用户回话为「那没有问题,就按放在公共区域的这一层的方案来实现」——回话指向的是该推荐整体而非仅色值半轴,故按已裁处置。该解释已写入第 15 节 Q1 裁决格供比对;**用户若不认这个解释,本条重开为待裁**,且重开只影响顶栏矩形的建造方,不动其余结论 | + +**轮次 2 总判**:三处补写已全部落卡;一处确认留档;一处按自判落并向用户报备(可重开)。落卡后本卡零待裁点。 + diff --git a/待办清单.md b/待办清单.md index c7de721a..90da9a00 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,16 +2,40 @@ 本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 +#### 流水线闸认锚式不含中文名文档——判据出处行锚对半数现行文档失效(2026-11-05 登记,来源=批次移交) + +**实况**:判据出处行锚的机检式 `ANCHOR_RE`(`tools/pipeline_gate.py:68`)字符类=`[A-Za-z0-9_\-.\\/]`,不含中文段——凡验收判据句落在中文名现行文档(本库 GDD 多为此形态:`HUD框架.md`/`关卡节点地图.md`/`待办清单.md`),机检一律认不到锚、判「判据出处行锚缺失」红。 +- 批次实证 reports/需求卡片-营地交互修复批-2026-11-05.md:R2/R3/R4/R8 四行的真出处是中文名文档,只能另挂一个 ASCII 路径文档当替身锚才过闸 +- 发现与依据 reports/复核-营地交互修复批-2026-11-05.md 第 22 条(`tools/pipeline_gate.py:68`) + +**改动面**:工具代码 + +**状态**:待执行 + +#### 层序落位单口检查的扫描面不含子目录——视图层插位不在该闸视野(2026-11-05 登记,来源=会话提及) + +**实况**:契约件 `game/tests/unit/contracts/source_guard_test.gd` 的「V1d 层序落位单口」用平铺列目录(`:86-99`,只收 `.gd` 文件名、不递归),注释 `:85` 仍写「presentation 无子目录」;而 `game/presentation/` 现实有 `views/` 与 `scenes/` 两目录,视图层全部文件不在该闸视野内。 +- 现状读数:全 presentation 的 `move_child` 共三处——合同函数 `apply_layer_contract()`(定义在该函数的落位件内)体内两处属唯一合法口;`game/presentation/views/ui_kit.gd:841` 在漏网区,是卡片内部布局重排、非模态层序,故当前无漏网真违规 +- 规则文本出处:`main_ui_build.gd:122`/`:156`/`:272`「插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child」 +- 启动待裁点(本条不预裁):是否真要把两目录纳入扫描面尚未确定;若纳入,判据须先收窄到「禁对根容器插位」,否则组件内重排会误红 + +**改动面**:工具代码 + +**状态**:待执行 + #### 营地交互修复批——大门无断点开局被开始层盖住/确认框钮序/营地顶栏无栏体(2026-11-05 登记,来源=会话提及) -**实况**:三件同批一张需求卡片,改动—验收对照表内一件一行。设计定稿=design/gdd/ux/camp.md v2.8 §顶栏、design/gdd/systems/表现与支撑/HUD框架.md §提示与弹层、design/art/art-bible.md v3.43 §6.2。 -- bug 证据①无断点开局被开始层盖住:`game/presentation/camp_shop_presenter.gd:85` 显开始层 → `game/presentation/main.gd:211-212`/`223-224` 先关营地再开局 → `main.gd:292` `_start_adventure()` 不隐藏;该动线测试与探针零覆盖 -- bug 证据②确认框否左是右:`game/presentation/views/confirm_dialog.gd:31-39` 加入序=取消→次→确认 -- bug 证据③营地顶栏无可见栏体:`game/presentation/views/camp_hud_band.gd:10-17`(件已装配落座,缺栏体底与落座定值) +**实况**:三件同批一张需求卡片,对照表内一件一行。 +- 需求卡片 reports/需求卡片-营地交互修复批-2026-11-05.md +- 两轮独立复核报告 reports/复核-营地交互修复批-2026-11-05.md +- 设计定稿 design/gdd/ux/camp.md v2.8 §顶栏、design/gdd/systems/表现与支撑/HUD框架.md §提示与弹层、design/art/art-bible.md v3.43 §6.2 +- bug 证据①被开始层盖住:`camp_shop_presenter.gd:85` 显层 → `main.gd:211-212`/`223-224` 先关营地再开局 → `main.gd:292` 不隐藏;该动线测试与探针零覆盖 +- bug 证据②否左是右:`views/confirm_dialog.gd:31-39` 加入序=取消→次→确认 +- bug 证据③无可见栏体:`views/camp_hud_band.gd:10-17`(件已装配落座,缺栏体底与落座定值) **改动面**:文档+游戏代码 -**状态**:文档设计已闭合+待写卡 +**状态**:文档设计已闭合+已写卡 #### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(2026-10-28 登记,来源=会话提及) From 0977a548bf79bb4e6a8d0621ef66feff54eeb087 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 01:38:27 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E5=86=99=E5=8D=A1=E6=94=B9=E5=88=A4(?= =?UTF-8?q?=E8=90=A5=E5=9C=B0=E4=BA=A4=E4=BA=92=E4=BF=AE=E5=A4=8D=E6=89=B9?= =?UTF-8?q?=C2=B7=E4=BB=B63=20=E7=BB=88=E8=A3=81=E8=90=BD=E5=8D=A1?= =?UTF-8?q?=EF=BC=8B=E8=BD=AE=E6=AC=A14=20=E7=8B=AC=E7=AB=8B=E5=A4=8D?= =?UTF-8?q?=E6=A0=B8):=20=E6=92=A4=E6=8E=89=E3=80=8C=E9=93=BA=E6=BB=A1?= =?UTF-8?q?=E8=89=B2=E9=9D=A2=E6=94=B6=E6=88=90=E5=B8=A6=E5=BD=A2=E5=8F=82?= =?UTF-8?q?=E7=9A=84=E9=80=9A=E7=94=A8=E5=87=BA=E5=8F=A3=E5=B9=B6=E6=94=B6?= =?UTF-8?q?=E7=BC=96=E4=B8=89=E5=A4=84=E5=90=8C=E5=BD=A2=E6=97=A7=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E3=80=8D=E9=82=A3=E4=B8=80=E7=89=88=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?,=20=E8=90=BD=E6=88=90=E7=94=A8=E6=88=B7=2011-06=20=E7=BB=88?= =?UTF-8?q?=E8=A3=81=E5=BD=A2=E6=80=81=E2=80=94=E2=80=94=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E4=BB=B6=E4=B8=80=E4=B8=AA=E5=85=B7=E5=90=8D=E5=87=BA=E5=8F=A3?= =?UTF-8?q?=20UiKit.camp=5Fhud=5Fband=5Fsurface(host)=20=E5=BD=A2=E5=8F=82?= =?UTF-8?q?=E5=8F=AA=20host=E3=80=81=E8=89=B2=E5=80=BC=20CAMP=5FHUD=5FBAND?= =?UTF-8?q?=3DColor(CAMP=5FHEADER=5FBAND,0.80)=EF=BC=8FPRESET=5FFULL=5FREC?= =?UTF-8?q?T=20=E9=93=BA=E6=BB=A1=EF=BC=8FMOUSE=5FFILTER=5FIGNORE=EF=BC=8F?= =?UTF-8?q?=E6=8C=82=E5=AE=BF=E4=B8=BB=EF=BC=8F=E8=BF=94=E5=8F=A5=E6=9F=84?= =?UTF-8?q?=E5=85=A8=E5=86=99=E6=AD=BB=E4=BD=93=E5=86=85,=20ui=5Fassembler?= =?UTF-8?q?.gd:224-230=E3=80=81ui=5Fkit.gd:486-491=E3=80=81:537=20?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E5=90=8C=E5=BD=A2=E5=86=99=E6=B3=95=E4=B8=8D?= =?UTF-8?q?=E6=94=B6=E7=BC=96=E4=B8=8D=E5=8A=A8(=E5=BD=A2=E7=8A=B6?= =?UTF-8?q?=E6=92=9E=E8=BD=A6=E2=89=A0=E5=90=8C=E4=B8=80=E4=BB=B6=E4=BA=8B?= =?UTF-8?q?:=20=E9=81=AE=E7=BD=A9=E9=81=AE=E6=8C=A1=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=EF=BC=8F=E9=A1=B5=E9=9D=A2=E5=BA=95=E8=89=B2=E6=A1=A3=E4=BD=8D?= =?UTF-8?q?=EF=BC=8FHUD=20=E5=B8=A6=E5=AE=9A=E5=80=BC=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=8F=98=E5=8C=96=E5=8E=9F=E5=9B=A0,=20=E7=BB=91=E8=B5=B7?= =?UTF-8?q?=E6=9D=A5=E5=8D=B3=E6=97=A2=E4=B8=8D=E5=86=85=E8=81=9A=E5=8F=88?= =?UTF-8?q?=E4=B8=8D=E4=BD=8E=E8=80=A6=E5=90=88=E4=B8=94=E4=B8=BA=E6=B2=A1?= =?UTF-8?q?=E5=88=B0=E6=9D=A5=E7=9A=84=E9=97=AE=E9=A2=98=E5=81=9A=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1);=20R9=20=E7=AD=89=E5=80=BC=E5=9B=9E=E5=BD=92?= =?UTF-8?q?=E5=88=A0=E3=80=81=E9=AA=8C=E6=94=B6=E5=9B=9E=20R1=E2=80=93R8?= =?UTF-8?q?=E3=80=81=E4=BC=B0=E6=97=B6=202.25=E2=80=932.5=E2=86=922.0=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=97=A5(=E6=97=A0=E6=94=B6=E7=BC=96?= =?UTF-8?q?=E6=97=A0=E4=B8=A4=E6=AC=A1=E5=85=A8=E6=A1=A3),=20=E5=A5=91?= =?UTF-8?q?=E7=BA=A64=20=E5=8A=A0=E3=80=8C=E7=A6=81=E5=BC=80=20color/pass?= =?UTF-8?q?=5Finput=20=E5=BD=A2=E5=8F=82=EF=BC=8B=E7=A6=81=E6=94=B6?= =?UTF-8?q?=E7=BC=96=E3=80=8D=E5=8F=8C=E7=A6=81=E3=80=81=E5=A5=91=E7=BA=A6?= =?UTF-8?q?5=20=E8=A1=A5=20ColorRect=20=E9=BB=98=E8=AE=A4=20STOP=20?= =?UTF-8?q?=E6=BC=8F=E8=AE=BE=E4=B8=8D=E6=8A=A5=E9=94=99=E7=9A=84=E6=97=81?= =?UTF-8?q?=E8=AF=81,=20=C2=A715=20Q1=20=E8=A3=81=E5=86=B3=E5=8F=B2?= =?UTF-8?q?=E8=A1=A5=E2=91=A2=E2=91=A3=E2=91=A4(=E5=90=AB=20Q4=20=E6=B6=88?= =?UTF-8?q?=E8=A7=A3=E2=80=94=E2=80=94=E7=94=A8=E6=88=B7=E5=90=A6=E5=AE=9A?= =?UTF-8?q?=E7=9A=84=E6=98=AF=E8=AF=A5=E5=91=88=E6=8A=A5=E5=89=8D=E6=8F=90?= =?UTF-8?q?=E8=80=8C=E9=9D=9E=E6=9F=90=E4=B8=80=E6=A1=88),=20=C2=A712=20?= =?UTF-8?q?=E6=9C=AB=E6=AE=B5=E7=95=99=E4=B8=A4=E7=89=88=E4=BD=9C=E5=BA=9F?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E7=9A=84=E5=90=88=E6=88=90=E7=90=86=E7=94=B1?= =?UTF-8?q?;=20=E8=BD=AE=E6=AC=A14=20=E7=94=B1=E7=AC=AC=E5=9B=9B=E4=B8=AA?= =?UTF-8?q?=E6=9C=AA=E5=8F=82=E4=B8=8E=E6=92=B0=E5=86=99=E5=8F=AA=E8=AF=BB?= =?UTF-8?q?=E5=AD=90=E4=BB=A3=E7=90=86=E5=A4=8D=E6=A0=B8=E7=BB=88=E6=80=81?= =?UTF-8?q?,=2017=20=E6=9D=A1:=20=E7=A1=AC=E4=BC=A4=202(R4=E3=80=8C?= =?UTF-8?q?=E6=8C=89=E8=A1=8C=E7=9F=A9=E5=BD=A2=E5=B7=A6=E4=BD=8D=E3=80=8D?= =?UTF-8?q?=E5=BF=85=E6=89=93=E7=A9=BA=E2=80=94=E2=80=94=E8=A1=8C=20ALIGNM?= =?UTF-8?q?ENT=5FCENTER=20=E4=B8=94=E5=8F=8C=E9=92=AE=E5=8F=AF=E8=A7=81?= =?UTF-8?q?=E5=AE=BD=20444<=E5=A3=B3=E5=AE=BD=20460,=20=E6=94=B9=E5=8F=96?= =?UTF-8?q?=20child(0)=20=E5=85=A8=E5=B1=80=E7=9F=A9=E5=BD=A2=E4=B8=AD?= =?UTF-8?q?=E5=BF=83;=20=5Fsmoke=5Fcamp=5Fhud=5Fband=20=E6=8E=A5=20=5Finit?= =?UTF-8?q?ialize=20=E4=B8=8D=E6=88=90=E7=AB=8B=E2=80=94=E2=80=94=E5=8F=AA?= =?UTF-8?q?=E8=B0=83=20=5Fsplit=5Ftail=20=E4=B8=94=E5=B0=BE=E6=AE=B5=20qui?= =?UTF-8?q?t,=20=E4=B8=8D=E8=A2=AB=20await=3D=E9=9D=99=E9=BB=98=E4=B8=8D?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=3D=E5=81=87=E7=BB=BF,=20=E6=94=B9=E6=8E=A5?= =?UTF-8?q?=20=5Fsplit=5Ftail=20await=20=E9=93=BE)=EF=BC=8F=E4=BA=8B?= =?UTF-8?q?=E5=AE=9E=E9=94=99=204(=E5=9B=9B=E6=B6=88=E8=B4=B9=E6=96=B9?= =?UTF-8?q?=E8=A1=8C=E5=8F=B7=2072/76-78/82=E2=86=9273/79-80/83=E3=80=81?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=A0=B9=20new=20=E4=BD=8D=E7=BD=AE=20camp?= =?UTF-8?q?=5Fscene=5Fview:13-14=E2=86=92scenes/camp=5Fscreen.gd:13-14=20?= =?UTF-8?q?=E5=B9=B6=E6=8D=AE=E6=AD=A4=E6=8E=A8=E7=BF=BB=E8=BD=AE=E6=AC=A1?= =?UTF-8?q?3=20=E6=B2=BF=E7=94=A8=E5=80=BC=E3=80=81modal=5Fshell=20?= =?UTF-8?q?=E4=B8=8D=E4=BA=A7=20HSeparator=E3=80=81:27-29=20=E6=98=AF?= =?UTF-8?q?=E5=90=8C=E6=9C=BA=E5=88=B6=E5=85=88=E4=BE=8B=E9=9D=9E=E5=8F=B3?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E5=85=88=E4=BE=8B)=EF=BC=8F=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=204(=C2=A78.4=20=E5=86=A0=E5=90=8D=20test-co?= =?UTF-8?q?nventions=E3=80=81R4=20=E8=AF=BB=20main.=5Fconfirm=5Fconsumer?= =?UTF-8?q?=20=E6=B3=A8=E6=98=8E=E6=8E=A2=E9=92=88=E5=85=88=E4=BE=8B=20:27?= =?UTF-8?q?2/:286=20=E4=B8=8E=E6=89=AB=E6=8F=8F=E5=B0=84=E7=A8=8B=E3=80=81?= =?UTF-8?q?=C2=A714=20=E8=81=8C=E8=B4=A3=E5=8F=A5=E5=8E=BB=E3=80=8C?= =?UTF-8?q?=E6=91=86=E6=94=BE=E5=BE=BD=E7=AB=A0=E3=80=8D=E3=80=81R2=20?= =?UTF-8?q?=E6=B3=A8=E6=98=8E=E7=89=B9=E6=9C=89=E6=8F=90=E4=BA=A4=E9=9D=A2?= =?UTF-8?q?=E7=94=B1=20R1=20=E6=89=BF=E8=BD=BD)=EF=BC=8F=E8=A1=A5=E5=86=99?= =?UTF-8?q?=203(=E5=BE=BD=E7=AB=A0=E8=87=AA=E9=AB=98=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E6=88=90=E5=9B=A0=2018px=20=E8=A1=8C=E9=AB=98=20FS=5FSUB?= =?UTF-8?q?=E3=80=81T3.4=20=E8=A1=A5=E5=85=A5=E6=A0=91=E5=89=8D=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=97=B6=E6=9C=BA=E3=80=81=E5=BE=85=E5=8A=9E=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E5=AE=9E=E5=86=B5=E4=B8=A4=E8=BD=AE=E2=86=92=E5=9B=9B?= =?UTF-8?q?=E8=BD=AE)=EF=BC=8F=E7=A1=AE=E8=AE=A4=205(=E6=94=B6=E7=BC=96?= =?UTF-8?q?=E7=89=88=E6=AE=8B=E7=95=99=E9=9B=B6=E3=80=8140=20=E5=A4=84?= =?UTF-8?q?=E8=A1=8C=E5=8F=B7=E3=80=81FULL=5FRECT=20=E9=93=BA=E6=BB=A1?= =?UTF-8?q?=E4=B8=8E=201280=20=E6=89=BF=E9=87=8D=E9=93=BE=E3=80=81const=20?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=E5=90=88=E6=B3=95=E6=80=A7=E3=80=81=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=E9=9D=A2=E4=B8=8E=20README=20=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E8=A1=8C=E9=80=90=E5=AD=97=E4=B8=80=E8=87=B4),=20=E6=9C=AA?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BE=85=E8=A3=81=E7=82=B9;=20=E4=B8=8D?= =?UTF-8?q?=E9=87=87=E4=BF=A1=201(=E5=A4=8D=E6=A0=B8=E8=80=85=E6=8A=8A=20p?= =?UTF-8?q?resentation=5Fencapsulation=5Fsweep=5Ftest.gd=20=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E8=AF=AF=E8=AE=B0=20unit/contracts/,=20=E5=AE=9E?= =?UTF-8?q?=E5=9C=A8=20unit/presentation/,=20=E7=BB=93=E8=AE=BA=E7=BB=8F?= =?UTF-8?q?=E5=9B=9E=E8=AF=BB=E6=88=90=E7=AB=8B=E5=B7=B2=E6=8C=89=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=B7=AF=E5=BE=84=E8=90=BD=E5=8D=A1);=20=E6=9C=AC?= =?UTF-8?q?=E6=AD=A5=E5=87=AD=E6=8D=AE:=20pipeline=5Fgate=20=E7=9B=B4?= =?UTF-8?q?=E8=B7=91=20PASS(=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE=2027/?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=8F=A3=E5=BE=84=202=20=E5=BC=A0=E5=85=A8?= =?UTF-8?q?=E8=BF=87)+tools/run=5Fall=5Fgates.sh=20--docs=20exit=3D0?= =?UTF-8?q?=E3=80=8C=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87,=20=E9=97=B8?= =?UTF-8?q?=E5=86=B5=E4=B8=8E=E5=9F=BA=E7=BA=BF=E4=B8=80=E8=87=B4=E6=97=A0?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BA=A2=E3=80=8D(=E5=9F=BA=E7=BA=BF=204a650?= =?UTF-8?q?d0e);=20=E5=BE=85=E5=8A=9E=E6=B8=85=E5=8D=95=E6=9C=AC=E6=9D=A1?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=BB=8D=20=E6=96=87=E6=A1=A3=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=B7=B2=E9=97=AD=E5=90=88=EF=BC=8B=E5=B7=B2=E5=86=99?= =?UTF-8?q?=E5=8D=A1,=20=E9=9B=B6=E5=BE=85=E8=A3=81=E7=82=B9,=20=E5=BC=80?= =?UTF-8?q?=E5=B7=A5=E9=97=A8=3DU0=20=E7=AD=89=E7=94=A8=E6=88=B7=E6=89=B9?= =?UTF-8?q?=E5=8D=A1,=20=E6=9C=AA=E5=90=88=E5=B9=B6=E6=9C=AA=E6=8E=A8?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reports/复核-营地交互修复批-2026-11-05.md | 49 ++++- reports/需求卡片-营地交互修复批-2026-11-05.md | 185 ++++++++++++------ 待办清单.md | 2 +- 3 files changed, 170 insertions(+), 66 deletions(-) diff --git a/reports/复核-营地交互修复批-2026-11-05.md b/reports/复核-营地交互修复批-2026-11-05.md index a19071b4..e4632a3f 100644 --- a/reports/复核-营地交互修复批-2026-11-05.md +++ b/reports/复核-营地交互修复批-2026-11-05.md @@ -59,4 +59,51 @@ | 4 | §3/§8 确认类 | 八处行号全对;`to_html(true)` 追加 α 两位、`6e4526cc` 串对;1280×48 尺寸链可达;「按类型找第一个 HBoxContainer」在本弹窗树内唯一 | `main.gd:211-212`/`:223-224`/`:292-308`、`ui_kit.gd:640`、`ui_assembler.gd:100/214/224-230`、`ui_kit.gd:189/190/200`、`standee_present_values_test.gd:36`、`scenes/camp_screen.tscn:7-11`、`project.godot:30-31` | 自判(留档) | 无改动 | | 5 | §15 Q1 | 裁决句字面只覆盖「色值放公共层」这条轴,「矩形谁建」是写卡段的展开 | 对话原话序列:写卡段推荐句为「色值进公共件、矩形留在顶栏件自建」,用户回话「那没有问题,就按放在公共区域的这一层的方案来实现」 | 待裁(复核者判)→ 按自判落并报备 | 卡内第 15 节与第 16 节各写入解释面一句;用户不认该读法即重开,重开范围仅止于矩形建造方 | -轮次 2 总判:三条补写已落卡,一条确认留档,一条按自判落并向用户报备。落卡后本卡零待裁点,可提交(合回仍等用户发令)。 +轮次 2 总判:三条补写已落卡,一条确认留档,一条按自判落并向用户报备。**该"自判闭合"事后被判无效**——见轮次 3 与卡片第 15/16 节的裁决史。 + +--- + +## 轮次 3(11-06 用户驳回"值与形拆两处"、改判整案收编之后;第三个只读子代理) + +| # | 卡内位置 | 发现 | 依据 | 去向 | 处置 | +|---|---|---|---|---|---| +| 1 | 契约4/§12 | 两处旧实现确未设 mouse_filter,ColorRect 默认即 STOP,出口默认值取 STOP 才不改三处旧遮罩语义 | `ui_assembler.gd:224-230`、`ui_kit.gd:486-491`;旁证 `ui_kit.gd:540`/`:864`、`camp_scene_view.gd:145` | 自判(确认) | 卡内补入旁证 | +| 2 | T3.2 | 三处既有调用点都是"挂宿主+把返回句柄当父用",转调在层序上等值 | `ui_assembler.gd:100-110`/`:214`/`:232-239`、`ui_kit.gd:511-514` | 自判(确认) | 无需改 | +| 3 | R9/T3.7 | `_modal_dim()` 经 `modal_shell()` 扇出 11 家消费件,R9 只点两处探针=覆盖不足 | `grep modal_shell(` 全仓 12 命中(含定义) | 补写 | 判据改源扫描式,并如实写明覆盖面(2 家断言+其余渲染闸与目检承接) | +| 4 | R5②⑤ | 1280 宽的承重链引错(非 tscn 锚点,是注册层 FULL_RECT);⑤ 全局矩形与局部 position 混写 | `camp_scene_view.gd:13-14`、`ui_registry.gd:26`/`:61` | 补写 | R5 改引注册层、⑤ 改 `get_global_rect()` | +| 5 | R9/T3.2 | "两文件 ColorRect 合计=1"算术不成立(`ui_kit` 实有 3 处自建) | `ui_kit.gd:487`/`:537`/`:862` | 补写 | 判据改为"两个被收编函数体内不再自建",范围显式声明,不写全仓唯一 | +| 6 | R9 | `:537` 页壳底板与出口形状相同、仅显式 STOP 与实色之差——是否同一件事,判为待裁 | `ui_kit.gd:536-542` | **待裁** | 新增 Q4(两案代价并列,不自决) | +| 7 | 多处 | 数字与交叉引用过期:显隐点 12→13(漏 `main_overlay_flow.gd:101`)、`views/` 自建先例 8 文件→7 文件 15 处、§5 与 §1 行数不一致、§12 步骤号未随重排更新、T4.1 应引 T3.9、§14/§15 把 tooltip 底误算进收编用途(`TIP_BG:190` 不经这两个函数) | `grep start_overlay.visible` 实测 13 处写点;`grep -c ColorRect.new()` views/*.gd 实测 7 文件 15 处 | 补写 | 全部按实测改正 | +| 8 | 轮次 2 记录 | 复核者上一轮给的 `camp_scene_view.gd:53` 才是正确行号,**写卡段上一轮"纠正复核者"改成的 `:51` 是错的** | 本轮 grep 复核 | 自判(推翻写卡段自己) | 全文改回 `:53`,卡片第 16 节轮次 3 记一条 | + +**写卡段对轮次 3 的不采信项**:无——这一轮唯一被推翻的是写卡段自己上一轮的判断。 + +**轮次 3 总判**:不能进实现段;除 Q4 待用户一句裁决外,其余补写已全部落卡。 + +## 轮次 4(11-06 撤收编版落卡后的独立复核) + +复核者=第四个未参与撰写的只读子代理(禁写禁跑,须给 `文件:行号`)。审的是撤收编后的终态(`UiKit.camp_hud_band_surface(host)` 单参具名出口+零收编)。它报 17 条:硬伤 2、事实错 2、一致性 4、补写 3、确认 5、外部 1;未新增待裁点。 + +| # | 卡片位置 | 发现 | 依据 | 去向 | 落卡 | +|---|---|---|---|---|---| +| 1 | R4 | 「按按钮行实际矩形算左位坐标」必打空:行 `alignment=CENTER`,双钮可见宽 216+12+216=444<壳宽 460,左位落 8px 空档 | `confirm_dialog.gd:29`、`ui_kit.gd:578`、`confirm_dialog.gd:20` | 补写 | R4 入口改取按钮行 child(0) 全局矩形中心并写入算式(与 T2.3 统一) | +| 2 | T3.4/§7/§11.5 | 「`_smoke_camp_hud_band()` 接进 `_initialize()`」不成立:`_initialize` 只调 `_split_tail()`,摘要与 `quit()` 在尾段,不被 await=静默不执行=假绿 | `system_test.gd:26-28`/`:35-48`/`:40` 行尾注/`:213-217` | 补写 | 三处同批改「接进 `_split_tail()` 的 await 链」,段内自持 instantiate/queue_free 照 `_smoke_run_lifecycle()` | +| 3 | §3.2/R3 | 四消费方行号偏一(卡 72/76-78/82,实 73/79-80/83;129 对) | `main_overlay_flow.gd:69-83`/`:125-129` | 自判 | 两处改正 | +| 4 | R5② | 营地场景根代码 new 的位置引错:`camp_scene_view.gd:13-14` 是命中区名表,new 在屏壳 | `camp_scene_view.gd:12-15`、`scenes/camp_screen.gd:13-14`、`camp_screen.tscn` 根锚 preset=15 | 自判 | 改引 `scenes/camp_screen.gd:13-14`(**同时推翻轮次 3 第 4 行沿用的 `camp_scene_view.gd:13-14`**) | +| 5 | R3 | 括注「`modal_shell()` 产 VBox+HSeparator」不实,HSeparator 是弹窗自加 | `ui_kit.gd:509-525`、`confirm_dialog.gd:26` | 自判 | 改为「产 CenterContainer+PanelContainer+VBox,不产横排」 | +| 6 | T3.3/§12 | 「`confirm_dialog.gd:27-29` 是容器右对齐先例」不准:该处 alignment 取 CENTER,是同机制先例 | `confirm_dialog.gd:29` | 自判 | 措辞改正,明写本件取 END 才是右对齐 | +| 7 | R6 | 「§8.4」未冠文档名,卡内无 8.4 小节 | `test-conventions.md` 正面效力自检条 | 自判 | 改「test-conventions §8.4」 | +| 8 | R4 | 读 `main._confirm_consumer`(私有)与 §12「不读私有」口径不一,但探针先例在案、扫描射程不含探针 | `ui_click_probe.gd:272`/`:286`、`presentation_encapsulation_sweep_test.gd:80-94`(该件在 `game/tests/unit/presentation/`,复核者误记 `unit/contracts/`) | 自判 | 主证改列 `is_adventure`,私有一读注明先例与射程;**路径误记已纠正,结论不受影响** | +| 9 | §14.1 | 出口职责句「返回句柄供顶栏件摆放徽章」与实际(返回值不用于摆位)不符 | 卡 T3.2/T3.3 | 自判 | 职责句改「供调用方读取与断言」,摆位归 T3.3 容器 | +| 10 | T3.0/L3.5 | 徽章自高≠48 只列描边一因,漏 18px 文字行高这一同因 | `ui_kit.gd:648`(Label 取 `FS_SUB`)/`:438` | 补写 | 两因并列,处置强度不变 | +| 11 | R2 | 判据非件1 改动特有面 | `ui_click_probe.gd:704` 既有同面断言 | 补写 | R2 注明特有提交面由 R1 承载、两行同批缺一即红 | +| 12 | 待办清单 | 实况仍写「两轮独立复核报告」 | `待办清单.md:30` | 补写 | 改「四轮」 | +| 13 | 全文 | 收编版残留扫描:无一处把收编/通用出口/color·pass_input/R9/改 `ui_assembler` 写成现行方案;T3.0–T3.7、R1–R8、白名单、L3、裁决史交叉引用零悬空 | 逐节比对+符号 grep | 确认 | 无需改 | +| 14 | 全文 | 40 处行号引用逐行打开无误(不含本表第 3、4 两条例外) | 复核者逐行比对 | 确认 | 无需改 | +| 15 | R5②③ | 未入树调 `set_anchors_preset(FULL_RECT)` 铺满成立、1280 宽承重链成立、色面必须入 `_init()`(入树前) | `ui_kit.gd:486-491`、`ui_assembler.gd:227`、`ui_registry.gd:61` | 确认 | 判据不变;按建议在 T3.4 补时机一句 | +| 16 | §2 实测句 | `Color(常量, α)` 构造子库内大量现役;const 引同文件 const 无库内先例,卡凭 11-06 临时工程实测(仓库无产物可复验=推测),失配属编译期 fail-fast | grep 零命中 | 确认 | 维持实测句,不设断言 | +| 17 | 契约/§5/§13/README | 契约4·5 与 §5 不做行无矛盾无空隙;两行新增能力名与 `tests/README.md:159/162/163` 逐字一致;预算实算正确(29 static func-6 私有=23→24) | 逐处比对 | 确认 | 无需改 | + +**写卡段对轮次 4 的不采信项**:1 条——第 8 行的文件路径误记(实际在 `game/tests/unit/presentation/`),已按正确路径落卡,其结论经回读成立。 + +**轮次 4 总判**:两条硬伤与四处事实错、四条一致性问题、三条补写全部落卡,未新增待裁点。撤收编后的终态设计至此经独立复核闭合:零待裁点,开工门=U0(用户批卡)。 diff --git a/reports/需求卡片-营地交互修复批-2026-11-05.md b/reports/需求卡片-营地交互修复批-2026-11-05.md index 2cc32f4f..99f5b079 100644 --- a/reports/需求卡片-营地交互修复批-2026-11-05.md +++ b/reports/需求卡片-营地交互修复批-2026-11-05.md @@ -42,7 +42,7 @@ 预览人审在案:2026-11-05 会话内实尺 HTML 预览(十张已过审场景层按 `camp_scene_layout.tres` 现值叠放+1:1 顶栏放大条+页内 WCAG 即时算式),用户审语「我看了预览的效果是OK的」,选定项=甲 深棕带 × 不透明度 0.80;快照文件 `chat-session-visualizations/…/visualizations/snapshot-444250310dc22234ac755b11fb31ce6885adb634b271c7083d53cc5d72b98640.html`(会话侧不可变快照,非仓库件)。像素级判据句已随附进 `camp.md` v2.8 §顶栏。 -色值落点定稿(用户 2026-11-05 裁「按放在公共区域的这一层」):`ui_kit.gd` 新增一枚带透明度的派生常量 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`——红绿蓝三分量继承既有 token、α 只此一处,顶栏件引用它。两件事已实测(引擎 4.7.2,工程外临时工程跑通后即删):①这种「const 作构造参数派生 const」的写法编译通过,`to_html(true)` 读数为 `6e4526cc`、三分量与母 token 逐一相等;②**色道存单精度浮点,`.a == 0.80` 判为 false**(实测 `a=0.80000001192093`)——所以断言一律写成 `to_html(true) == "6e4526cc"` 或与同构造值整体等值比较,禁写 `.a == 0.80`,否则实现正确而断言红,最容易的自救就成了放宽阈值(触 test-conventions §8.5 禁止削弱测试)。 +顶栏这块带子的落点定稿(用户 2026-11-05/11-06 四次裁决的终态,全程留痕见第 15 节 Q1 裁决史):**一个 UI 物件的外观不得拆两处**——色值与透明度、铺满方式、不吃点击、挂上宿主、返回句柄这五件事全在公共件 `ui_kit.gd` 的**一个具名公开出口**里;出口只接「挂到谁身上」一个形参,色与点击语义写死在它内部(它们是这块带子的定义,不是调用方每次要重述的选择)。同时**不收编盘上任何同形旧实现**:`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537` 只是形状撞车、变化原因各不同,把它们绑进同一个出口=为没到来的问题做设计,并把回归面扩到全站弹窗底与两个全屏页。顶栏件只调该出口一次,不再自建 ColorRect。两项引擎实测(4.7.2,工程外临时工程跑通后即删,与本条封装方式无关,仍成立):①`Color(母token, 0.80)` 形态的 const 派生编译通过,`to_html(true)` 读数 `6e4526cc`、三分量与母 token 逐一相等;②**色道存单精度浮点,`.a == 0.80` 判为 false**(实测 `a=0.80000001192093`)——断言一律写 `to_html(true) == "6e4526cc"` 或整体等值比较,禁浮点分量直等,否则实现正确而断言红,最容易的自救就成了放宽阈值(触 test-conventions §8.5 禁止削弱测试)。 --- @@ -62,7 +62,7 @@ - `game/presentation/views/confirm_dialog.gd:31-39`:`_init()` 内三颗钮的 `btn_row.add_child()` 次序=取消 → 次 → 确认。 - 信号按按钮对象路由、不按位次:`confirm_dialog.gd:32/35/38` 各自 `.pressed.connect(_emit_cancelled/_emit_secondary/_emit_confirmed)`——换序不改路由,文案也不用来认钮。 -- 四处消费方(全部经同一件):删档确认 `game/presentation/main_overlay_flow.gd:129`(钮标「删除」/「取消」)、大门有断点三出口 `main_overlay_flow.gd:76-78`(「继续上一段冒险」/「开始新冒险」/「取消」)、大门无断点 `main_overlay_flow.gd:82`(「是」/「否」)、篝火离场 `main_overlay_flow.gd:72`(「离开」/「留下」)。 +- 四处消费方(全部经同一件):删档确认 `game/presentation/main_overlay_flow.gd:129`(钮标「删除」/「取消」)、大门有断点三出口 `main_overlay_flow.gd:79-80`(「继续上一段冒险」/「开始新冒险」/「取消」)、大门无断点 `main_overlay_flow.gd:83`(「是」/「否」)、篝火离场 `main_overlay_flow.gd:73`(「离开」/「留下」)。 - 现有断言只核「次钮在两出口形态下隐藏」(`ui_click_probe.gd:696`),**无一条核位次**。 ### 3.3 件3——顶栏件有装配无栏体 @@ -71,7 +71,7 @@ - 徽章现状:`camp_hud_band.gd:15-16` 设 `PRESET_CENTER_RIGHT` 后 `position=Vector2(-160, 8)`(注释自标「代拍板 3」)。 - 徽章自高=48px:`game/presentation/views/ui_kit.gd:640` 的 `panel_flat(..., Vector4(14, 8, 14, 8))` 给图标 32px 上下各加 8px 内边距=48px,正等于栏高——**顶距 8px 在栏内放不下**,落座只能顶格。 - 装配链在位:`game/presentation/main_ui_build.gd:120-121` 造件并调 `set_hud_band()` → `game/presentation/views/camp_scene_view.gd:88-91` 把件塞进 `_hud_slot` → `camp_scene_view.gd:188` 预留位 `custom_minimum_size=(0, layout.hud_band_h)`,取数自 `game/data/tables/camp_scene_layout.tres` 的 `hud_band_h=48`(schema 见 `game/data/schemas/camp_scene_layout_table.gd:7`)。 -- 铺满矩形的既有实现盘上已有两份、形态不同:`game/presentation/ui_assembler.gd:224-230` `overlay_bg(host, color) -> ColorRect`(**外部调用者零**,仅被该文件自身 `:100`、`:214` 用)与 `game/presentation/views/ui_kit.gd:486-491` `_modal_dim()`(私有,仅 `:511` 一处用)。四处同形的用途实为三种意图——亮底面板(`BG_BRIGHT`)/模态压暗遮罩(黑 0.6)/tooltip 遮罩底,本批顶栏带是第四种意图(常驻 HUD 条且不吃点击)。**本批不复用那一份、也不抽新工厂**(依据与留痕见第 12 节与第 15 节);顶栏件按 `views/` 现行形态自己新建矩形——该目录直接 `ColorRect.new()` 的现存先例共 8 个文件 15 处(`battle_end_overlay.gd` 4 处、`board_fx_view.gd` 3 处、`camp_scene_view.gd` 1 处等)。 +- 盘上「新建 ColorRect+铺满宿主」的写法现有三处,意图各不相同,本批一处都不动:`game/presentation/ui_assembler.gd:224-230` `overlay_bg(host, color)`(亮底面板与模态压暗遮罩共用,外部调用者零、仅该文件自身 `:100`/`:214` 用,**没设 mouse_filter**);`game/presentation/views/ui_kit.gd:486-491` 私有 `_modal_dim()`(同一份东西的第二实现,同样不设 mouse_filter,仅 `:511` 经 `modal_shell()` 一处用、经该壳扇出 11 家模态消费件);`ui_kit.gd:537` 页壳底板(`page_shell()` 内,**显式**写 `MOUSE_FILTER_STOP`、色取 `BG_BRIGHT`/`BG_ROOT` 实色,其后紧跟石纹层与暗角层作兄弟节点)。三者与顶栏带只有形状撞车:前两者的变化原因是「遮罩挡不挡点击」,页壳底板的变化原因是「这块屏用哪档底色」,顶栏带的变化原因是「这条 HUD 带的定值」。另:顶栏件 `camp_hud_band.gd:14` 现役已在调 `UiKit.keystone_badge()`——视图件引公共件在本文件内就有先例,故出口落 `ui_kit`、不经装配器。用户 2026-11-06 终裁=**顶栏带的外观整件封进公共件一个具名出口,同形旧实现不收编**(早先两版——「值进公共件+矩形视图自建」与「四份同形收成带形参的通用出口」——都已作废,逐次理由见第 15 节 Q1 裁决史与第 12 节末段)。 - 落位带实际背景:营地背景层 `game/assets/art/scene/scene_camp_bg.png` 的 y0–48 带均色实测 `#DEEAE7`(逐像素取样,画面最上的可点物件是大门,顶缘 y=250,故 48px 带整条落在远景天空上)。徽章烛光黄底 `#F6C667` 对该天空 1.29:1;定值 `#6E4526`×0.80 叠后呈色 `#84664D`,对天空 4.27:1、对徽章底 3.31:1。 - 像素取样先例:`game/tests/unit/presentation/terrain_tile_sets_test.gd:107` 已用 `img.get_pixel()` 在单元套内取样贴图。 - 无 golden 锚:`game/tests/golden/README.md`「未建锚面备案」明写营地屏无改前 golden 锚,该面等价证据=探针/单元断言+实机目检。 @@ -80,11 +80,11 @@ ## 4. 设计契约(红线,违反=L3 停机呈报) -- 🔴 **契约1|冒险启动路径的隐藏点收一处**:**限「开局」与「续玩」两条路径**——隐藏责任只许落在 `_start_adventure()`(开局)与 `_resume_from_menu()`(续玩)两个函数内部。第 3.1 条的先例形态即本契约依据。**本契约不管其余开层自管点**:全库开始层显隐共 12 处(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`),那些是选档层/生涯页/终局回菜单等各自开合面的自管句,本批一律不动、也不受本契约约束。禁再新增**开局或续玩路径**上的第三处隐藏点,禁把隐藏写回 gate 出口(调用方)。 +- 🔴 **契约1|冒险启动路径的隐藏点收一处**:**限「开局」与「续玩」两条路径**——隐藏责任只许落在 `_start_adventure()`(开局)与 `_resume_from_menu()`(续玩)两个函数内部。第 3.1 条的先例形态即本契约依据。**本契约不管其余开层自管点**:全库开始层显隐共 13 处(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`/`:101`),那些是选档层/生涯页/终局回菜单等各自开合面的自管句,本批一律不动、也不受本契约约束。禁再新增**开局或续玩路径**上的第三处隐藏点,禁把隐藏写回 gate 出口(调用方)。 - 🔴 **契约2|`on_camp_closed()` 语义不动**:`camp_shop_presenter.gd:85` 那句显示开始层是本批禁改项——篝火离场与 Esc 返回主菜单两条动线靠它。改动=回退主菜单功能。 - 🔴 **契约3|位次改动只动 `add_child` 次序**:`confirm_dialog.gd:31-39` 的三句连接(`.pressed.connect(...)`)、三个钮对象的创建与文案参数一律不动;四处消费方的 `open()`/`open_three()` 传参零改动。位次与语义的绑定由第 8 节 R4 行的坐标真点断言钉。 -- 🔴 **契约4|取色单点在 UiKit,禁视图内裸写色值**:栏体色=`ui_kit.gd` 新增的派生常量 `CAMP_HUD_BAND`(`Color(CAMP_HEADER_BAND, 0.80)`,三分量继承在册 token),顶栏件与断言都只引用它;**禁在视图件写 `Color8(110, 69, 38)` 类裸值、禁把 α 写在视图里**。禁的是新增**语义色**,不是禁止派生常量——同文件在册先例:`HP_BAR_TRACK`(`ui_kit.gd:200`,注「视图原裸值收口单点——非色板新增」)、`TOAST_BG`(`:189`)与 `TIP_BG`(`:190`)都是带 α 的派生常量。依据=`ui_kit.gd:2` 文件头「呈现层共享小部件工厂+全站取色/描边唯一出口——各视图禁内联 Color8/StyleBoxFlat 字面量,换皮只改本文件」。色板基线不受本枚常量影响:`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 形态从不进快照。 -- 🔴 **契约5|栏体不挡命中**:栏体 ColorRect 必须 `mouse_filter = Control.MOUSE_FILTER_IGNORE`,顶栏件自身 `camp_hud_band.gd:13` 的同款设置是既有锚。漏设=48px 带内的点击被栏体吃掉,与 `camp.md:37` 禁区纪律叠加成「顶排物件点不动」。 +- 🔴 **契约4|这块带子的全部外观封在公共件的同一个具名出口里**:`ui_kit.gd` 出一个公开出口(本卡定名 `camp_hud_band_surface(host)`,实现可按同文件命名习惯微调后缀形态,见白名单第 6 条),**色值与 α、铺满、不吃点击、挂上宿主、返回句柄**五件事全在它内部;**形参只有 host 一个**——禁把颜色与「吃不吃点击」开成形参(那等于把这块带子的定义推回调用方每次重述,也正是本契约要禁的拆两处)。色值取派生常量 `Color(CAMP_HEADER_BAND, 0.80)`(三分量继承在册 token,同文件先例 `TOAST_BG:189`/`TIP_BG:190`/`HP_BAR_TRACK:200` 注「收口单点——非色板新增」)。**同时禁收编无关同形件**:`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537` 三处原样不动,出口也不得被它们转调——形状撞车不等于同一件事,绑起来就得到一个同时要回答「页底用哪档色」「遮罩挡不挡点击」「HUD 带穿不穿点击」三个变化原因的函数,并把回归面扩到全站弹窗底(用户 2026-11-06 终裁,见第 15 节 Q1 裁决史④与第 12 节末段)。依据=`ui_kit.gd:2`「全站取色/描边唯一出口,换皮只改本文件」+ code-conventions §1.1 一个类一个职责、单一变化原因判据。色板快照不受新常量影响(`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 从不进快照)。 +- 🔴 **契约5|不吃点击由出口内部保证**:栏体必须 `mouse_filter = Control.MOUSE_FILTER_IGNORE`,写死在出口内、不靠调用方记得设(顶栏件自身 `camp_hud_band.gd:13` 同款设置是既有锚)。漏设=48px 带内点击被栏体吃掉,与 `camp.md:37` 禁区纪律叠加成「顶排物件点不动」;且 ColorRect 默认即 STOP,漏设不会报错只会静默失守(同文件 `:540` 显式 STOP、`:864` 与 `camp_scene_view.gd:145` 须显式 IGNORE 为旁证)。本条与契约4 合起来把这块带子的外观压到一处。 - 🔴 **契约6|本批禁写文档面**:`design/` 整个目录、`待办清单.md`/`体验验证清单.md` 两份根流程清单、`game/tests/README.md` 一律禁改(能力两行已由写卡段落表,实现批只改名实相符);产出物只准落 `reports/`。 - 🔴 **契约7|禁变异类测试与撤销类取证**(test-conventions §8.4,全项目红线):本批所有断言从真实入口进入、绑被测改动特有的可观测提交面,禁人为制造红、禁回退 hunk 取证。 @@ -95,7 +95,7 @@ **做**: 1. `_start_adventure()` 入口收一个隐藏点,摘掉 `main.gd:194` 的冗余句; 2. `ConfirmDialog` 三颗钮的 `add_child` 次序整体镜像; -3. `ui_kit` 加一枚带 α 的派生色值常量 + `CampHudBand` 用它新建可见栏体、徽章改右对齐容器顶格落座; +3. 公共件出一个具名出口封装这块带子的全部外观(色值与 α、铺满、不吃点击、挂上宿主、返回句柄)+ `CampHudBand` 改调该出口、徽章落座改右对齐容器; 4. 上述三件各自的测试(对照表 8 行 R1–R8)+能力清单两行(写卡段已落)+回归面确认。 **不做(含去向)**: @@ -105,6 +105,7 @@ | 「破坏性确认与常规确认禁止同位」(art-bible §4.5 清单 #7 后半句/interaction-patterns §7)在弹窗层的闭合 | 该句系 2026-09-14 文档骨架初版提交随件进入,全库查无用户裁决记录,且 battle.md:335 指定的无障碍基线权威(HUD框架 §可访问性基线)不含它——它是不是用户定的规则,本身待裁 | 本卡不动它;现状四处同位属既有状态,不随本批产生或消失。要不要闭合,归用户另发令,另立待办条目 | | 顶栏栏内加第二件(存档位号/关卡语境等) | 用户 2026-11-05 裁「栏内只放钥石徽章一件」,左位不放件 | 无 | | 营地场景层坐标、素材、七物件摆放的重排 | 属场景重排批,不与本卡混装(用户 2026-11-05 口径:代码批与素材批分两张卡) | 归那一批自己的卡 | +| 把盘上三处同形铺面写法(`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`)收敛成一个通用铺面器 | 形状同而变化原因不同(遮罩遮挡语义/页面底色档位/HUD 带定值);收编=把三个不相关变化原因绑进一个带形参的函数,回归面扩到全站弹窗底与两个全屏页底板,而这三个地方今天都没有待改的东西——为没到来的问题做设计(用户 2026-11-06 终裁,第 15 节 Q1 裁决史④) | 无。三处原样留底;日后若真出现意图相同的第二个消费者,届时按 code-conventions §1.1 重议 | | 营地屏 golden 建锚 | 无改前基线可留底,新建锚不属缺陷修 | 不登记(顶栏观感走目检) | | `main.gd:137` 的 boot 期 `start_overlay.visible = true` | 启动默认态,与三件无关 | 无 | @@ -136,15 +137,15 @@ |---|---|---|---|---| | `game/presentation/main.gd` | 组合根:冒险启动与确认分派编排 | 改动(`_start_adventure()` 加一句;`_start_adventure_from_menu()` 删一句) | 行为改动 | R1、R2 | | `game/presentation/views/confirm_dialog.gd` | 通用确认弹窗视图件 | 改动(三句 `add_child` 次序,**不动成员面**) | 行为改动 | R3、R4 | -| `game/presentation/views/ui_kit.gd` | 呈现层共享工厂与全站取色单点 | 改动(新增一枚带 α 派生常量 `CAMP_HUD_BAND`;**不新增方法**) | 行为改动(顶栏呈现的数据源) | R5、R6 | -| `game/presentation/views/camp_hud_band.gd` | 营地顶栏 HUD 件 | 改动(新建栏体矩形引用该常量+徽章落座改右对齐容器) | 行为改动 | R5、R6 | +| `game/presentation/views/ui_kit.gd` | 呈现层共享工厂与全站取色单点 | 改动(新增带 α 派生常量 + **新增一个具名公开出口**,该出口承载这块带子的全部外观;既有三处同形铺面写法一律不动) | 行为改动(顶栏呈现源) | R5、R6 | +| `game/presentation/views/camp_hud_band.gd` | 营地顶栏 HUD 件 | 改动(调该出口得栏体,不再自建 ColorRect;徽章落座改右对齐容器) | 行为改动 | R5、R6 | | `game/probe/ui_click_probe.gd` | 真点探针:大门两分支段所在宿主 | 改动(同段追加断言) | 非行为改动(测试面) | R1、R2、R4、R7、R8 | -| `game/tests/system_test.gd` | 系统测试宿主 | 改动(新增 `_smoke_camp_hud_band()`,接进 `_initialize()`) | 非行为改动(测试面) | R5、R8 | +| `game/tests/system_test.gd` | 系统测试宿主 | 改动(新增 `_smoke_camp_hud_band()`,接进 `_split_tail()` 的 await 链〔`:40-44`〕) | 非行为改动(测试面) | R5、R8 | | `game/tests/unit/presentation/camp_scene_layout_test.gd` | 营地屏布局与顶栏禁区单元套 | 改动(新增 `_test_hud_band_contrast_over_bg()`) | 非行为改动(测试面) | R6 | | `game/tests/unit/presentation/confirm_dialog_test.gd` | 确认弹窗按钮位次与语义单元套 | **新增** | 非行为改动(测试面) | R3 | | `game/tests/README.md` | 能力清单 | 改动(两行,写卡段落) | 非行为改动 | — | -结构图与第 12 节「落位与可复用性预检」互指:改动件四个、新增件仅一枚单元套;顶栏矩形为何不复用盘上现成那两份、也不抽新工厂,理由见第 12 节与第 15 节的留痕句。 +结构图与第 12 节「落位与可复用性预检」互指:改动件七个(四件表现层+三件测试面)、新增件仅一枚单元套;这块带子的全部外观收在公共件的同一个具名出口里,出口只服务这块带子(判据见第 15 节 Q1 裁决史④与契约4)。 --- @@ -153,11 +154,11 @@ | 行 | 需求点(判据出处行锚) | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | |---|---|---|---|---|---|---|---| | R1 | 件1 大门无断点按「是」后开始层不可见(`design/gdd/ux/camp.md:16`+`design/gdd/systems/冒险循环/关卡节点地图.md:78`) | `main.gd:211-212` 分支经 `main.gd:292` 新入口隐藏 | `ui_click_probe.gd:_ready`(大门无断点段追加「确认后开始层不可见」) | 探针 | 营地场景根大门命中区 → `ProbeUiUtil.push_click_at` 真点 → 弹窗「是」钮 `ProbeUiUtil.push_click(_confirm_btn)` 真点 | `main.ui.screens.start_overlay.visible` 取值(被测改动特有的可见性提交面) | 既有:营地大门两分支确认与断点续玩接线 | -| R2 | 件1 开局落在节点图选路停点(`design/gdd/ux/level-map.md:69`;停点句唯一出处=`design/gdd/systems/冒险循环/关卡节点地图.md` 第 78 行「关卡开始 → 呈现节点图选路停点」) | 同 R1 | `ui_click_probe.gd:_ready`(同段追加「关卡画面在位」) | 探针 | 同 R1 真点链(大门命中区真点 →「是」钮真点) | `run_scope.is_adventure` + `main.ui.modals.run.level_screen.visible`(停点呈现面,非终态推断) | 既有:营地大门两分支确认与断点续玩接线 | -| R3 | 件2 两出口与三出口形态的按钮位次=推进→次→维持(`design/gdd/ux/camp.md:16`;位次定值唯一出处=`design/gdd/systems/表现与支撑/HUD框架.md` 第 90 行「三条纪律·其三」) | `confirm_dialog.gd:31-39` 三句 `add_child` 次序镜像;**成员面与连接一律不动** | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order` | 单元 | 视图件对外入口 `ConfirmDialog.open(text, confirm_label, cancel_label)`/`open_three(text, confirm_label, secondary_label, cancel_label)`——即四消费方实际用的那两个公开方法(`main_overlay_flow.gd:72/76/82/129`) | 测试自己传入三档互异文案(如「推进」「次推」「维持」),从弹窗节点向下按类型定位那个横排容器(HBoxContainer),断言其 `get_children()` 的**子节点顺序**:双钮形态 child(0) 文案=「推进」且 **child(2)** 文案=「维持」——次钮在 `open()` 里只是 `visible=false`、不从子节点表移除(`confirm_dialog.gd:47`),双钮形态下子节点仍为 3 个,按下标断必须取 child(2)(写 child(1) 必红);三钮形态 child(0)/child(1)/child(2)=「推进」「次推」「维持」。另配非空前置:按钮行子节点数=3(防扫描面缩水静默绿)。按类型定位可成立已核:该弹窗树内只有这一个 `HBoxContainer`(`UiKit.modal_shell()` 产 VBox+HSeparator,`ui_kit.gd:509-525`;`UiKit.button()` 无子节点)。判据面=树的公开子节点序(同 `system_test.gd:81-89` 断言层序的先例形态),**不读任何私有成员**,避开 `architecture.md:168` 视图单元接口与 `:323` 禁直引实现内部 | 新增:确认弹窗按钮位次与语义同钉 | -| R4 | 件2 左位那颗真点下去就是推进(`design/gdd/ux/camp.md:16`;位次定值出处同 R3) | 同 R3 | `ui_click_probe.gd:_ready`(大门无断点段内,`push_click_at` 打按钮行左位中心坐标) | 探针 | 弹窗开态下按按钮行实际矩形算左位坐标真点 | `_confirm_consumer` 在真点前为 `"gate_new"`、真点后走开局分支(`run_scope.is_adventure` 翻转+`_confirm_consumer` 清零)——位次改动若错接,坐标命中的将是「否」,该面即红 | 新增:确认弹窗按钮位次与语义同钉 | -| R5 | 件3 顶栏栏体几何与徽章落座(`design/gdd/ux/camp.md:41`) | `camp_hud_band.gd:10-17` 新建栏体矩形引用新常量+徽章落座改右对齐容器;`ui_kit.gd` 加 `CAMP_HUD_BAND` | `system_test.gd:_smoke_camp_hud_band` | 集成 | 真实工程 boot 后经装配链 `main_ui_build.gd:120-121` → `camp_scene_view.gd:88-91` 落座;**须先开营地**(营地屏 boot 态为隐,`camp_scene_view.gd:51`),走 `main._camp_pres.open_camp()` 探针既有用法并 await 排版帧,再读 `main.ui.modals.camp.scene.hud_band`(`camp_scene_view.gd:49` 的公开句柄) | ①栏体色 `color.to_html(true) == "6e4526cc"`——即规格「`#6E4526` × α0.80」的字面编码;**禁写 `.a == 0.80`**(实测色道单精度使该比较为 false,见第 2 节实测句);②栏体矩形铺满:`size == Vector2(1280, 48)`;③`mouse_filter == MOUSE_FILTER_IGNORE`;④栏体是顶栏件的**第一个子节点**(子节点序=绘制序,色面必须先入才不盖徽章);⑤徽章全局矩形 `position.y == 0` 且 `end.x == 顶栏全局矩形 end.x − 12`(x 向缺判据则位次改错仍绿,故与顶格同批配齐)。前置量测=T3.0 读 `get_combined_minimum_size().y` 须为 48(描边若计入得 50 → 触第 10 节 L3 第 5 条) | 新增:营地顶栏栏体定值与落座 | -| R6 | 件3 栏体对落位带底色对比 ≥3:1(`design/gdd/ux/camp.md:41`+`design/art/art-bible.md:452`) | 同 R5 | `camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` | 单元 | 单元从 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口取带高,读现行背景贴图 `res://assets/art/scene/scene_camp_bg.png` 该带像素(`load(...)` 取纹理再 `get_image()`+`get_pixel`,先例=`terrain_tile_sets_test.gd:105-107` 的全像素扫描) | 规格侧算式核验(与 R5 分工:R5 核实装等值,本行核规格值本身达标)=测试内按 `camp.md:41` 写死 `#6E4526` 与 α0.80,与该带实测均色 alpha 合成后算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 正面效力自检配齐。**禁把视图件的 color 直接当预期值喂进算式**(那是 oracle 镜像,test-conventions §8.7 判红) | 新增:营地顶栏栏体定值与落座 | +| R2 | 件1 开局落在节点图选路停点(`design/gdd/ux/level-map.md:69`;停点句唯一出处=`design/gdd/systems/冒险循环/关卡节点地图.md` 第 78 行「关卡开始 → 呈现节点图选路停点」) | 同 R1 | `ui_click_probe.gd:_ready`(同段追加「关卡画面在位」) | 探针 | 同 R1 真点链(大门命中区真点 →「是」钮真点) | `run_scope.is_adventure` + `main.ui.modals.run.level_screen.visible`(停点呈现面,非终态推断)。**本行是开局终态在位断言,被测改动的特有提交面由 R1 承载**——`is_adventure` 在既有探针已断(`ui_click_probe.gd:704`),把件1 的改动绑在它上不成立,故本行的效力=与 R1 同批缺一即红 | 既有:营地大门两分支确认与断点续玩接线 | +| R3 | 件2 两出口与三出口形态的按钮位次=推进→次→维持(`design/gdd/ux/camp.md:16`;位次定值唯一出处=`design/gdd/systems/表现与支撑/HUD框架.md` 第 90 行「三条纪律·其三」) | `confirm_dialog.gd:31-39` 三句 `add_child` 次序镜像;**成员面与连接一律不动** | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order` | 单元 | 视图件对外入口 `ConfirmDialog.open(text, confirm_label, cancel_label)`/`open_three(text, confirm_label, secondary_label, cancel_label)`——即四消费方实际用的那两个公开方法(`main_overlay_flow.gd:73`/`:79`/`:83`/`:129`) | 测试自己传入三档互异文案(如「推进」「次推」「维持」),从弹窗节点向下按类型定位那个横排容器(HBoxContainer),断言其 `get_children()` 的**子节点顺序**:双钮形态 child(0) 文案=「推进」且 **child(2)** 文案=「维持」——次钮在 `open()` 里只是 `visible=false`、不从子节点表移除(`confirm_dialog.gd:47`),双钮形态下子节点仍为 3 个,按下标断必须取 child(2)(写 child(1) 必红);三钮形态 child(0)/child(1)/child(2)=「推进」「次推」「维持」。另配非空前置:按钮行子节点数=3(防扫描面缩水静默绿)。按类型定位可成立已核:该弹窗树内只有这一个 `HBoxContainer`(`UiKit.modal_shell()` 产 CenterContainer+PanelContainer+VBox,不产横排——`ui_kit.gd:509-525`;`confirm_dialog.gd:26` 那条 HSeparator 与 `UiKit.button()` 的产件都不是 HBoxContainer)。判据面=树的公开子节点序(同 `system_test.gd:81-89` 断言层序的先例形态),**不读任何私有成员**,避开 `architecture.md:168` 视图单元接口与 `:323` 禁直引实现内部 | 新增:确认弹窗按钮位次与语义同钉 | +| R4 | 件2 左位那颗真点下去就是推进(`design/gdd/ux/camp.md:16`;位次定值出处同 R3) | 同 R3 | `ui_click_probe.gd:_ready`(大门无断点段内,`push_click_at` 打**推进钮自身**全局矩形中心) | 探针 | 弹窗开态下按类型定位按钮行 `HBoxContainer`,取其 child(0) 的 `get_global_rect()` 中心真点——**禁按「行矩形左位」算坐标**:该行 `alignment=CENTER`(`confirm_dialog.gd:29`),双钮可见宽 216+12+216=444<壳宽 460(`ui_kit.gd:578` 钮默认 `min_w=216.0`/`confirm_dialog.gd:20` 壳 `Vector2(460,0)`),左右各留 8px 空档,打行矩形左位会落空 | 主证=`run_scope.is_adventure` 翻转+`_confirm_consumer` 清零;真点前置读 `main._confirm_consumer == "gate_new"`(该私有的直读属**探针既有先例**,`ui_click_probe.gd:272`/`:286` 现役同款,封装扫描件只覆盖 flow 件的 `root._`——见 `presentation_encapsulation_sweep_test.gd:80-94`,故本行不新开辟读面)——位次若错接,坐标命中的将是「否」,主证面即红 | 新增:确认弹窗按钮位次与语义同钉 | +| R5 | 件3 顶栏栏体几何与徽章落座(`design/gdd/ux/camp.md:41`) | `camp_hud_band.gd:10-17` 改为调公共件出口得栏体+徽章落座改右对齐容器;`ui_kit.gd` 加带 α 常量与公开出口 | `system_test.gd:_smoke_camp_hud_band` | 集成 | 真实工程 boot 后经装配链 `main_ui_build.gd:120-121` → `camp_scene_view.gd:88-91` 落座;**须先开营地**(营地屏 boot 态为隐,`camp_scene_view.gd:53`),走 `main._camp_pres.open_camp()` 探针既有用法并 await 排版帧,再读 `main.ui.modals.camp.scene.hud_band`(`camp_scene_view.gd:49` 的公开句柄) | ①栏体色 `color.to_html(true) == "6e4526cc"`——即规格「`#6E4526` × α0.80」的字面编码;**禁写 `.a == 0.80`**(实测色道单精度使该比较为 false,见第 2 节实测句);②栏体铺满:出口产出的那块色面 `get_global_rect()` 宽=1280、高=48——**宽度不是 tscn 锚点给的**(营地场景根由屏壳代码 new——`game/presentation/scenes/camp_screen.gd:13` `CampSceneView.new()` + `:14` `add_child`,该根自身无锚无尺寸;承重实为 `ui_registry.gd:26`/`:61` 打的 FULL_RECT 注册,屏壳 `camp_screen.tscn` 根锚 preset=15 满铺),故实现首步先按 T3.0 同款时机量一次顶栏件现尺寸,再以此定断言值;③`mouse_filter == MOUSE_FILTER_IGNORE`;④栏体是顶栏件的**第一个子节点**(子节点序=绘制序,色面必须先入才不盖徽章);⑤徽章 `get_global_rect().position.y == 0` 且 `get_global_rect().end.x == 顶栏 get_global_rect().end.x − 12`(x 向缺判据则位次改错仍绿,故与顶格同批配齐;两项都用全局矩形,不与局部 `position` 混写)。前置量测=T3.0 读 `get_combined_minimum_size().y` 须为 48(描边若计入得 50 → 触第 10 节 L3 第 5 条) | 新增:营地顶栏栏体定值与落座 | +| R6 | 件3 栏体对落位带底色对比 ≥3:1(`design/gdd/ux/camp.md:41`+`design/art/art-bible.md:452`) | 同 R5 | `camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` | 单元 | 单元从 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口取带高,读现行背景贴图 `res://assets/art/scene/scene_camp_bg.png` 该带像素(`load(...)` 取纹理再 `get_image()`+`get_pixel`,先例=`terrain_tile_sets_test.gd:105-107` 的全像素扫描) | 规格侧算式核验(与 R5 分工:R5 核实装等值,本行核规格值本身达标)=测试内按 `camp.md:41` 写死 `#6E4526` 与 α0.80,与该带实测均色 alpha 合成后算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 test-conventions §8.4(正面效力自检)配齐。**禁把视图件的 color 直接当预期值喂进算式**(那是 oracle 镜像,test-conventions §8.7 判红) | 新增:营地顶栏栏体定值与落座 | | R7 | 回归·件1 未动的三条动线(`design/gdd/ux/camp.md:16`/`:22`) | 无改动(`main.gd:208`/`214`、`main_esc_flow.gd:46`) | `ui_click_probe.gd:_ready`(既有大门两分支段+篝火离场段现有断言,本批零改写) | 探针 | 既有真点链 | 既有绑定面原样:`is_adventure` 不翻转、营地场景根 `visible`、`_camp_data()["balance"]` 不变 | 既有:营地大门两分支确认与断点续玩接线 | | R8 | 回归·件2 未触的路由与件数断言(`design/gdd/ux/camp.md:16`;位次与件数定值出处同 R3) | 无改动 | `ui_click_probe.gd:_ready`(次钮隐藏断言 `:694`)+ `system_test.gd:_smoke_boot_chain`(开始层三钮计数 `:195`) | 探针/集成 | 既有真点与 boot 链 | 既有绑定面:`_secondary_btn.visible` 为假(两出口形态)、开始层可见钮数 == 3 | 既有:营地大门两分支确认与断点续玩接线 | @@ -194,28 +195,29 @@ 产出:位次成为规格钉死的呈现定值,推进钮恒为该按钮行的 child(0)。 -### T3|营地顶栏可见栏体(≈0.75 工作日) +### T3|营地顶栏可见栏体(整件外观封在公共件一个具名出口,≈0.75 工作日) -取用:`UiKit.CAMP_HEADER_BAND`(`ui_kit.gd:195`,色值单点);`UiKit.keystone_badge(text: String) -> KeystoneBadge`(`ui_kit.gd:655`);`CampHudBand._init()`(`camp_hud_band.gd:10`);`Control.PRESET_FULL_RECT`/`Control.MOUSE_FILTER_IGNORE`(引擎枚举,栏体铺满与不吃命中的既有形态见 `camp_hud_band.gd:11-13`)。 +取用:`UiKit.CAMP_HEADER_BAND`(`ui_kit.gd:195`,色值单点);`UiKit.keystone_badge(text: String) -> KeystoneBadge`(`ui_kit.gd:655`);`CampHudBand._init()`(`camp_hud_band.gd:10`,该件 `:14` 现役已在调 `UiKit`)。**盘上三处同形铺面写法不在取用面**(`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`)——本批既不让顶栏件复用它们,也不把它们收编进出口,理由见第 3.3 条、契约4 与第 12 节末段。 -- [ ] T3.0 前置读数:**入树之后**再量——`KeystoneBadge` 是 PanelContainer(`ui_kit.gd:634-656`),最小尺寸由容器排版产出,刚 `new()` 未入树时读数不可信。落点=在系统测试宿主里经 `main.ui.modals.camp.scene.hud_band` 链取**已在树的现成徽章实例**,先开营地并 `await` 排版帧(帧数口径同 T1.4),再读 `get_combined_minimum_size().y`(`ui_kit.gd:640` 的 `panel_flat(...Vector4(14,8,14,8))` 推得 32+8+8=48,但 `border_w=1` 是否计入最小尺寸未证实——**这是推导,不是实测量**)。读数=48 → 按 T3.2 顶格落座;读数≠48(如 50)→ 停步走第 10 节 L3 第 5 条呈报,禁自行改栏高或改徽章内边距。读数与取数时机一并写进实现报告。 -- [ ] T3.1 实装(**Q1 已裁·2026-11-05 用户裁「按放在公共区域的这一层」**):先在 `game/presentation/views/ui_kit.gd` 的色板段(`CAMP_HEADER_BAND` 定义在 `:195`,同文件带 α 先例见 `:189`/`:190`)新增 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`,注记沿 `HP_BAR_TRACK`(`:200`)的「视图原裸值收口单点——非色板新增」说法。再在 `game/presentation/views/camp_hud_band.gd` 的 `_init()` 内、挂徽章**之前**新建栏体矩形:`ColorRect.new()` + `color = UiKit.CAMP_HUD_BAND` + `set_anchors_preset(Control.PRESET_FULL_RECT)` + `mouse_filter = Control.MOUSE_FILTER_IGNORE`(契约5)+ `add_child`——顶栏件自建是该目录现行形态(8 个文件 15 处先例,含本件自己的徽章),**不复用 `ui_assembler.gd:224` 的 `overlay_bg()`**(它的头注 `:2-3` 把服务对象写成「供组合根接线」,且外部调用者零;视图引它会新开一条 `views → ui_assembler` 路径),**也不抽新工厂**(意图三分,见第 12 节留痕句)。子节点序即绘制序,故栏体必须先入、徽章后入。 -- [ ] T3.2 实装(**Q2 已裁乙·2026-11-05 用户裁**):徽章落座改为「栏体内加一层右对齐容器」——新增 `HBoxContainer` 铺满栏体(`set_anchors_preset(Control.PRESET_FULL_RECT)` 之后置 `offset_right = -12.0`)、`alignment = BoxContainer.ALIGNMENT_END`、`mouse_filter = Control.MOUSE_FILTER_IGNORE`,徽章作该容器子件、由容器摆位,余额位数增宽时自然向左展;替换 `camp_hud_band.gd:15-16` 现值(`PRESET_CENTER_RIGHT` + `position=(-160, 8)`,系无栏体时代把 x 写死的代拍板)。容器右对齐先例=`confirm_dialog.gd:27-29`(HBoxContainer+alignment 摆位,非手算偏移)。内缩定值 12px 不变(白名单第 3 条)。 -- [ ] T3.3 验收:`game/tests/system_test.gd` 新增 `_smoke_camp_hud_band()` 并接进 `_initialize()`,实现 R5 的五项判据(色值走 `to_html(true) == "6e4526cc"` 或整体等值比较,**禁浮点分量直等**;`size == Vector2(1280, 48)`;`mouse_filter == MOUSE_FILTER_IGNORE`;栏体是顶栏件的第一个子节点;徽章 `position.y == 0` 且 `end.x == 顶栏 end.x − 12`)。 -- [ ] T3.4 验收:`game/tests/unit/presentation/camp_scene_layout_test.gd` 新增 `_test_hud_band_contrast_over_bg()` 实现 R6:读 `hud_band_h` 与背景贴图该带**全像素**均色(取样法照 `terrain_tile_sets_test.gd:105-107` 的双层全扫,非步长抽样),规格值 `#6E4526`/α0.80 按 `camp.md:41` 在测试内写死后合成算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 配齐。 -- [ ] T3.5 验证:新增两测先行孤立跑绿,再并入 `--code` 全套;营地屏截图面由 `render_gate` 的七屏段随批产出,作 R5/R6 的对照取证。 -- [ ] T3.6 目检登记:营地顶栏观感(「成一条可辨窄带、徽章落在带内」)写进实现报告实机目检总清单,登记即完成义务。 +- [ ] T3.0 前置读数:**入树之后**再量——`KeystoneBadge` 是 PanelContainer(`ui_kit.gd:634-656`),最小尺寸由容器排版产出,刚 `new()` 未入树时读数不可信。落点=在系统测试宿主里经 `main.ui.modals.camp.scene.hud_band` 链取**已在树的现成徽章实例**,先开营地并 `await` 排版帧(帧数口径同 T1.4),再读 `get_combined_minimum_size().y`(`ui_kit.gd:640` 的 `panel_flat(...Vector4(14,8,14,8))` 加在 32px 图标上下各 8=48,两个未证实项都能把它推离 48:①`border_w=1` 是否计入最小尺寸;②徽章内 Label 用 18px 档(`ui_kit.gd:648` 取 `FS_SUB`,常量见 `:438`),字体行高若大于 32 则最小高由文字而非图标决定——**这两条都是推导,不是实测量**)。读数=48 → 按 T3.3 顶格落座;读数≠48(如 50)→ 停步走第 10 节 L3 第 5 条呈报,禁自行改栏高或改徽章内边距。读数与取数时机一并写进实现报告。 +- [ ] T3.1 实装·出口本体(Q1 终裁·11-06 用户裁「整件外观封在公共件一个具名出口,同形旧实现不收编」):`game/presentation/views/ui_kit.gd` 色板段加 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`(带 α 先例=同文件 `:189`/`:190`,收口非新增语义色的先例=`:200`);工厂段加公开出口 **`static func camp_hud_band_surface(host: Control) -> ColorRect`**,体内依次=新建 ColorRect + `rect.color = CAMP_HUD_BAND` + `rect.set_anchors_preset(Control.PRESET_FULL_RECT)` + `rect.mouse_filter = Control.MOUSE_FILTER_IGNORE` + `host.add_child(rect)` + `return rect`。**形参只有 host**:色、α、铺法、点击语义全写死在体内——这就是「一个物件的外观不得拆两处」的确切实现形(对照两个作废形态:值在公共件+铺法在视图=拆两处;出口开 color/pass_input 形参=把物件定义推回调用方,并为无关同形件预留通用性)。出口名钉死在这块带子上,不作通用铺面器。 +- [ ] T3.2 实装·顶栏件:`camp_hud_band.gd` 的 `_init()` 内在挂徽章之前调 `UiKit.camp_hud_band_surface(self)`(契约5 的 IGNORE 由出口内部保证,调用方没有可以忘记的形参);子节点序即绘制序,栏体先入、徽章后入。本文件内不再出现 `ColorRect.new()`。 +- [ ] T3.3 实装·徽章落座(**Q2 已裁乙·2026-11-05 用户裁**):`camp_hud_band.gd` 内加一层右对齐容器承载徽章——`HBoxContainer` 铺满栏体(`PRESET_FULL_RECT` 后置 `offset_right = -12.0`)、`alignment = BoxContainer.ALIGNMENT_END`、**`mouse_filter = Control.MOUSE_FILTER_IGNORE`(容器自身默认 STOP,与出口内那处 IGNORE 是两个点、必须同批落,漏一处顶排物件就点不动)**;替换 `camp_hud_band.gd:15-16` 现值(`PRESET_CENTER_RIGHT` + `position=(-160, 8)`),位数增宽自然向左展。容器摆位机制先例=`confirm_dialog.gd:27-29`(同件同机制,那里 alignment 取 CENTER、这里取 END);内缩 12px 见白名单第 3 条。 +- [ ] T3.4 验收:`game/tests/system_test.gd` 新增 `_smoke_camp_hud_band()`,**接进 `_split_tail()` 的 await 链**(`:40-44` 那一串 `await _smoke_*()` 之后追加 `await _smoke_camp_hud_band()`),**不是接进 `_initialize()`**——`_initialize()` 只调一句 `_split_tail()`(`:26-28`),而摘要打印与 `quit()` 都在该尾段末尾(`:47-48`),协程段不被 await 就会在首个 await 处被 quit 截断=静默不执行=假绿(该教训已写在 `:40` 行尾注里)。段内自持 `load("res://presentation/main.tscn").instantiate()`+`root.add_child`+await 帧、末了 `queue_free()`,形态照 `_smoke_run_lifecycle()`(`:213-217`/`:206-207`)。实现 R5 的五项判据(色值走 `to_html(true) == "6e4526cc"` 或整体等值比较,**禁浮点分量直等**;`get_global_rect()` 的尺寸=顶栏件实测宽 × 48;`mouse_filter == MOUSE_FILTER_IGNORE`;栏体是顶栏件的第一个子节点;徽章全局矩形 `position.y == 0` 且 `end.x == 顶栏 end.x − 12`)。**出口须在 `_init()`(入树前)被调**:`set_anchors_preset(PRESET_FULL_RECT)` 是把四边 offset 归零的入树前动作,与 `_modal_dim()`/`overlay_bg()` 的现役形态一致。 +- [ ] T3.5 验收:`game/tests/unit/presentation/camp_scene_layout_test.gd` 新增 `_test_hud_band_contrast_over_bg()` 实现 R6:读 `hud_band_h` 与背景贴图该带**全像素**均色(取样法照 `terrain_tile_sets_test.gd:105-107` 的双层全扫,非步长抽样),规格值 `#6E4526`/α0.80 按 `camp.md:41` 在测试内写死后合成算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 配齐。 +- [ ] T3.6 验证:两条新测先行孤立跑绿再并入 `--code` 全套。本批不触任何既有遮罩/亮底/页壳件,故无等值回归义务——回归证据=第 5 节七条未改动动线与全站弹窗现有断言在 `--code` 全档中仍绿。营地屏截图面由渲染闸的七屏段随批产出,作 R5/R6 对照取证。 +- [ ] T3.7 目检登记:营地顶栏观感(「成一条可辨窄带、徽章落在带内」)登记一行进实现报告实机目检总清单,登记即完成义务。 -产出:`CampHudBand` 成为顶栏唯一呈现件(栏体色面+右对齐容器+徽章),顶栏色值单点=新增的 `UiKit.CAMP_HUD_BAND`(其三分量继承 `CAMP_HEADER_BAND`,α 只此一处)。 +产出:`CAMP_HUD_BAND` + `camp_hud_band_surface()` 是这块带子外观的唯一出处(同文件相邻两段);`CampHudBand` 的外观=该出口+右对齐容器+徽章三件,视图内无一处自建 ColorRect;盘上三处同形写法原样未动。 ### T4|产出物落位卡(≈0.5 工作日,末卡) -- [ ] T4.1 代码域全档全绿后、合并前写 `reports/iter<53>-实现报告-<日期>.md`:各卡状态+实机目检总清单(含 T3.6)+遗留读数义务+实现决定记录(超白名单的微决策逐条登记)。 +- [ ] T4.1 代码域全档全绿后、合并前写 `reports/iter<53>-实现报告-<日期>.md`:各卡状态+实机目检总清单(含 T3.7)+遗留读数义务+实现决定记录(超白名单的微决策逐条登记)。 - [ ] T4.2 写 `reports/iter<53>-反哺点-<日期>.md`:第 6 节每条命中项都要有落点(哪一节、改成什么、依据什么);无命中也要写「本批零反哺点」并附核过哪几处。 - [ ] T4.3 核对 `game/tests/README.md` 两行新增能力名与实际落地函数名逐字一致(名实不符=停卡退回写卡段)。 - [ ] T4.4 呈报合回清单,等用户发令再走收口(冻结→件核→并 master→重塑→删区)。 -**估时合计 ≈1.75–2.0 个工作日**。切分预案:T3 若因取样件超 0.25 日未完,先合 T1+T2(两件互不依赖),T3 另起一批——第 1 节三件本无耦合。 +**估时合计 ≈2.0 个工作日**(T1 0.25+T2 0.5+T3 0.75+T4 0.5)。T3 的 0.75=出口一枚(十行内)+顶栏件改两处(调出口+落座容器)+两条新测+孤立验证。无收编、无等值回归、无两次全档义务。 --- @@ -228,7 +230,7 @@ 2. R4 坐标真点无法区分左位钮与相邻钮(按钮行 `alignment=CENTER` 下实际矩形与规格不符)=位次与语义绑不上观测面,触 test-conventions §8.4「判据绑不上=停卡呈报」出口; 3. 位次改动使 `system_test.gd:195` 或 `ui_click_probe.gd:694`(次钮隐藏断言)失守=契约3 被违,禁改判据求绿; 4. 机检预算互斥限**本批触碰的 presentation 面文件**:`tools/gdstyle_gate.py:45` 的扫描面=`game/core`/`game/data`/`game/presentation`,脚本头注(`:8`)明写排除 probe/sim/tests——故新增单元套**不在** S13 射程,该条只对 `main.gd`/`confirm_dialog.gd`/`camp_hud_band.gd` 生效。若这三个件的 S13 键与第 13 节预算数字冲突且无豁免通道=上游缺陷,停卡退回写卡段,禁自造第三条路(本技能第 13/14 节)。 - 5. T3.0 量得徽章 `get_combined_minimum_size().y` ≠ 48(描边宽 1px 若计入最小尺寸则推得 50)=「顶格落座」在 48px 栏内放不下,第 2 节与 `camp.md:41` 的推导句失效,停步呈报,禁自行改栏高、改徽章内边距或放宽 R5 判据。 + 5. T3.0 量得徽章 `get_combined_minimum_size().y` ≠ 48(描边宽 1px 计入、或 18px 文字行高高于 32px 图标使最小高由文字决定,两条都会推得 >48)=「顶格落座」在 48px 栏内放不下,第 2 节与 `camp.md:41` 的推导句失效,停步呈报,禁自行改栏高、改徽章内边距或放宽 R5 判据。 --- @@ -238,8 +240,8 @@ 2. 新单元套件的套件注册名与 `run_all.gd` 册内落点序。 3. **徽章水平内缩 12px**:`camp.md` v2.8 只钉「右位顶格」未钉内缩值,取 UiKit 钮间距同档 12(先例=`confirm_dialog.gd:28` `separation=12`)。 4. R6 取样密度——照 `terrain_tile_sets_test.gd:105-107` 的**双层全像素扫描**(该件不是步长抽样,卡内早先写的「每 4 行×每 8 列」口径作废)。 -5. `_smoke_camp_hud_band()` 在 `_initialize()` 内的插入位(不改变既有段序)。 -6. 新色值常量的命名(卡内暂定 `CAMP_HUD_BAND`)——只要它是「一枚带 α 的派生常量、不加方法、不进色板五件表」这个形态已定,名字可由实现定;若实现改成加方法或加语义色,触契约4 与第 13 节,属 L3 呈报。 +5. `_smoke_camp_hud_band()` 在 `_split_tail()` await 链内的插入位(不改变既有段序;接进 `_initialize()` 不成立——见 T3.4)。 +6. 出口与常量的命名(卡内定名 `UiKit.camp_hud_band_surface()` 与 `CAMP_HUD_BAND`)——可按同文件命名习惯微调后缀形态;**若实现把它改成带 color/pass_input 形参的通用铺面器、改成「值与形分两处」、或另起第二家公共出口,一律触契约4,属 L3 呈报**。 不在白名单:α 值、栏底色、栏高、位次序、隐藏点数量与位置——这些是规格钉死项,改动一律 L3 呈报。 @@ -249,14 +251,14 @@ | 新增/改动件 | 复用面(盘上已有什么) | 唯一面 | 分层与交互面 | |---|---|---|---| -| 栏体矩形(T3.1,Q1 已裁) | 盘上已有两份同形实现:`ui_assembler.gd:224-230` `overlay_bg()`(**外部调用者零**,仅该文件自身 `:100`/`:214` 用)与 `ui_kit.gd:486-491` `_modal_dim()`(私有,仅 `:511` 一处用)。本批两份都不复用、也不抽新工厂,按 `views/` 现行形态自建(该目录直接 `ColorRect.new()` 有 8 个文件 15 处先例,含本件自己的徽章)。不引装配器的依据:它头注 `:2-3` 把服务对象写成「返回句柄供组合根接线」「接线拓扑唯一归组合根」,现网引用方全在组合根与屏壳侧(`main_ui_build.gd:7`、`scenes/adventure_screen.gd:21`、`scenes/menu_screen.gd:15`),视图引它等于新开一条 `views → ui_assembler` 路径。**不抽工厂的留痕句见本节末** | 顶栏呈现件唯一 = `CampHudBand`;色值单点 = 新增的 `UiKit.CAMP_HUD_BAND`(`Color(CAMP_HEADER_BAND, 0.80)`,三分量继承在册 token、α 只此一处),视图与断言都引用它,禁在别处出现第二组「同一色+同一 α」 | 呈现层内;读数仍经 `camp_data()` 投影单口不变(`camp_hud_band.gd:26-27`),本批零新增跨层引用路径 | +| 顶栏带外观出口(T3.1/T3.2) | **现成件已复用**:徽章走 `UiKit.keystone_badge()`(`ui_kit.gd:655`,`camp_hud_band.gd:14` 是现役调用方),色值走在册 `CAMP_HEADER_BAND`(`:195`)。盘上另有三处「新建 ColorRect+铺满宿主」的同形写法(`ui_assembler.gd:224-230` 外部调用者零、`ui_kit.gd:486-491` 私有且仅 `:511` 一处用、`ui_kit.gd:537` 页壳底板)——**本批三者都不复用也不收编**:它们的变化原因是遮罩遮挡语义与页面底色档位,与本带的 HUD 定值无关。出口落 `ui_kit`、不经装配器,因顶栏件已在引用 `UiKit`,而 `views/` 引装配器零先例。早先两版判据(「两份都不复用、按 views 现行形态自建」与「四份同形收成带形参的通用出口」)都已作废,逐次理由见第 15 节 Q1 裁决史 | 这块带子的外观全仓一处定义=`CAMP_HUD_BAND`+`camp_hud_band_surface()`(同文件相邻两段);禁在别处出现第二组「同一色+同一 α」;出口内 `MOUSE_FILTER_IGNORE` 写死——旁证:同文件 `:540` 页壳底板显式写 STOP、`:864` 与 `camp_scene_view.gd:145` 须显式写 IGNORE,可见 ColorRect 默认即 STOP,漏设不报错只会静默吃掉顶排点击 | 呈现层内一个纯构建出口,零状态、零信号、不持引用;顶栏读数仍经 `camp_data()` 投影单口(`camp_hud_band.gd:26-27`);`ui_assembler.gd` 与 `modal_shell()`/`page_shell()` 一律不动 | | 开始层隐藏(T1.1) | **复用** 既有语义句本身——`main.gd:194`/`:246` 就是同一条责任,本卡把它收进被调方 | 开局路径隐藏点唯一(`_start_adventure()`),续玩路径唯一(`_resume_from_menu()`);其余 10 处开层自管点不在本契约射程(契约1 已收窄) | 组合根内;不触 Core,不新增信号 | | 按钮位次(T2.1) | 无新增件——只镜像既有三句的次序。**本卡不新增成员、不新增公开出口**:位次判据走公开子节点序(R3),语义判据走坐标真点(R4) | 位次定值只写在视图件 `_init()` 一处,四处消费方零副本 | 呈现层;信号对象路由不动;依 `architecture.md:168`「视图单元接口=信号外发+setter 只读注入」与 `:323`「禁直引呈现层实现内部」,不为测试开成员面(该写法曾在本卡出现过,已撤回,登记在第 16 节轮次 2) | -| 徽章落座(T3.2) | 已有做同样事的东西:`HBoxContainer` + `alignment` 摆位(`confirm_dialog.gd:27-29`)——**Q2 已裁乙(2026-11-05 用户裁)**,采容器右对齐形态;另两案(锚点生长、`size_updated` 回调)作淘汰留档见第 15 节 | 内缩值 12px 只在落座一处出现一次,禁在断言里另写第二套推导 | 呈现层内 | +| 徽章落座(T3.3,Q2 已裁乙) | 已有做同样事的东西:`HBoxContainer` + `alignment` 摆位机制(`confirm_dialog.gd:27-29`——该件 `alignment` 取的是 `ALIGNMENT_CENTER`,**是同一机制的先例、不是右对齐的先例**;本件同处改取 `ALIGNMENT_END` 即右对齐)——**Q2 已裁乙(2026-11-05 用户裁)**,采容器右对齐形态;另两案(锚点生长、`size_updated` 回调)作淘汰留档见第 15 节 | 内缩值 12px 只在落座一处出现一次,禁在断言里另写第二套推导 | 呈现层内 | | `confirm_dialog_test.gd`(T2.2 新增) | 盘上无 ConfirmDialog 专属单元套(grep 命中面=`system_test.gd`/探针的宿主级引用与 `README.md:159` 能力行);同目录 `camp_scene_layout_test.gd:12-13` 是可参照的套件形态(入口 `_initialize()`+`call_deferred`),不是可复用的承载 | 位次判据只此一处;R4 探针判据与之互补(一个核序、一个核语义) | 测试层;经 `ConfirmDialog.open()/open_three()` 公开入口进出,断言只读公开子节点序与 Button 的公开 `text`——「读私有成员」的写法曾在 R3 出现,已按 `architecture.md:323` 撤回 | -| `system_test.gd:_smoke_camp_hud_band`/`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg`(T3.3/T3.4) | **扩展**既有套件,不新建宿主;取样法复用 `terrain_tile_sets_test.gd:105-107` 的双层全扫+`get_pixel` 路径 | 几何判据落系统测试、对比判据落布局单元套——两处职责已由套件名界定,禁第三处重复断言 | 测试层 | +| `system_test.gd:_smoke_camp_hud_band`(T3.4)/`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg`(T3.5) | **扩展**既有套件,不新建宿主;取样法复用 `terrain_tile_sets_test.gd:105-107` 的双层全扫+`get_pixel` 路径 | 几何判据落系统测试、对比判据落布局单元套——两处职责已由套件名界定,禁第三处重复断言 | 测试层 | -**同形不同意图的留痕句(本卡有意接受这份重复)**:铺满一个控件的纯色矩形这件事,盘上已有两份实现、四种用途——亮底面板(`ui_assembler.gd:100` 用 `BG_BRIGHT`)、模态压暗遮罩(`:214` 用黑 0.6)、tooltip 遮罩底(`ui_kit.gd:486`),与本批的常驻 HUD 条带。三次法则的触发条件是「同一功能第三次出现」,此处是同一**形状**第四次出现、同一**意图**第一次出现,故不抽公共工厂;把它抽成一个件得到的只会是"随便铺个矩形",属形状公因数而非概念。若日后出现第二个**同意图**件(又一个常驻 HUD 条带),再行提取。该取舍按 Fowler 技术债四象限属「有意且审慎」一格,凭据即本句+实现报告的实现决定记录。(本句由用户 2026-11-05 的架构取舍口径定:体量未变、矛盾未现,不为未到来的问题预先设计。) +**为什么出口只服务这块带子(2026-11-06 用户终裁;本段取代本卡此前两份互相矛盾的留痕句——「接受第四处重复故不抽工厂」与「四份同形收成带形参的通用出口」)**:用户裁的是两件事的合成。①**一个物件的外观不得拆两处**——色值、α、铺法、点击、挂载这五件事必须在同一个出口内定义,不许「常量在公共件、矩形在视图」;那等于要求每个改它的人同时记得两处,漏一处就得到一个自相矛盾的物件。本会话确有改漏实例:同一组色值要在两份文档里成对改,卡内同步面还写了「禁单改一处」的防呆,却在代码面上造出另一个新的成对结构。②**抽不抽公共器不由形状撞车决定**——不得为了「全仓只剩一处 ColorRect 铺满」这种读数好看,把遮罩、页底、HUD 带三个变化原因绑进一个带形参的通用器;那既不内聚(一个函数回答三个不相关的问题)也不低耦合(改顶栏带要回归全站弹窗底与两个全屏页),而这三处今天都没有待改的东西。**早先那份「三次法则、意图未第三次出现故不抽工厂」的留痕句,其动机经用户点破是「少动文件」而非纪律,作废**;但据此反弹出的「整案收编+形参化点击语义」同样作废。代价面(`ui_kit` 公开方法 23→24 须修订登记基线)见第 13 节例外申报;回归面因不收编而留在本件射程内,估时相应回落。 --- @@ -273,38 +275,43 @@ | 文件 | 基线登记(`tools/gdstyle_baseline.txt`) | 本批预期 | |---|---|---| | `game/presentation/main.gd` | 第 11 行 `quality/max-public-methods = 32` | 不动公开方法面(函数体内加一句+另一函数删一句)→ 读数 32 持平 ✓ | -| `game/presentation/views/ui_kit.gd` | 第 12 行 `quality/max-public-methods = 23` | 本批**只加一枚 `const`、不加方法** → 读数 23 持平 ✓。色板快照面也不动:`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 常量从不进快照(同文件 `TOAST_BG`/`TIP_BG` 即此形态) | +| `game/presentation/views/ui_kit.gd` | 第 12 行 `quality/max-public-methods = 23` | 本批**加一枚 `const` + 加一个公开出口方法** → 读数预期 23→**24**,超登记值 → 见下方「指标例外申报」。色板快照面不受常量影响(`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 从不进快照,同文件 `TOAST_BG`/`TIP_BG` 即此形态);但**方法数是真升**,不申报就是假绿 | | `game/presentation/views/confirm_dialog.gd` | 无登记键(在默认阈值内) | 仅语句重排,分支与返回数不变 ✓ | -| `game/presentation/views/camp_hud_band.gd` | 无登记键 | `_init()` 加栏体与容器两处构造、无分支 → 分支数不变 ✓ | +| `game/presentation/views/camp_hud_band.gd` | 无登记键 | `_init()` 换成调出口+加一层容器,分支数不变 ✓ | | `game/probe/ui_click_probe.gd` | 无登记键(探针大文件另有基线口径) | 同段追加断言,函数不拆;若单函数分支数超默认,按 L3 第 4 条呈报而非自拆 | | `game/tests/system_test.gd` | 无登记键 | 新增一个私有函数,公开方法面不变 ✓ | -**指标例外申报**:无。本批不申请任何超默认项。 +**指标例外申报(本批一项)**:`game/presentation/views/ui_kit.gd|quality/max-public-methods` 由登记值 23 升至 24,起因=用户 2026-11-06 裁「一个物件的外观不得拆两处」,要求在公共件出一个公开出口承载这块带子的整件外观。处置=实现批跑出该新键后,把 `tools/gdstyle_baseline.txt` 该行改为 24 并在提交信息里申报(基线手动修订须经用户批准——本条即申报与请求)。除此之外本批不申请任何超默认项。 --- ## 14. 拆分边界纪律 -本卡非拆分/收敛/重构类:**不适用**——零拆出单元(无新私有函数族、无新件从存量文件迁出、无段件/状态件),唯一新增是一个视图单元套件,其职责语句=「钉确认弹窗的按钮位次与语义绑定」,单一变化原因=位次定值变更(第 12 节复用面同载)。code-conventions §1.1 拆分判据与本节互指,实现期若发现需要拆 `camp_hud_band.gd` 或 `main.gd` 才落得下,属卡片未预见的结构动作=L3 呈报,禁为凑绿硬拆。 +本批新增的可复用单元只有公共件里那一个出口函数,故本节条款的适用面=该出口,判据权威=code-conventions §1.1 拆分判据,此处不复制定文: + +1. **它有可陈述的职责与单一变化原因**:`UiKit.camp_hud_band_surface()` 的职责语句=「把营地顶栏这块带子按定值铺满宿主并挂上去,返回句柄供调用方读取与断言」;单一变化原因=这块带子的外观要变(以后要求圆角、内缩、换遮挡方式,都只改这一个函数+那枚常量)。它不承载玩法状态、不承载文案、不做判定、不接形参选择;徽章的摆位不由它负责(归顶栏件内那层右对齐容器,T3.3)。 +2. **集体身份成立**:它服务的是营地顶栏这一块具名带子,不是「所有 ColorRect 铺满动作」的收纳盒。**故意不做**成通用铺面器:若把模态遮罩、页壳底板一并收进来(用户 2026-11-06 否掉的形态),这个单元就从「一块带子的定义」变成「三种不相关意图的并集」——正是拆分判据要拦的形态。 +3. **「过阈值」只作发现信号**:本批不以任何计数变好看为目标;公开方法数上升是它的代价(见第 13 节例外申报),不是它的动机。 +4. **拆不出内聚单元即停卡呈报**:若实现期发现该出口必须塞进条件分支才能同时容纳顶栏带与其它用途,说明本卡对「只有这一个消费者」的判断失真——停卡呈报退设计段重议,禁硬并为凑绿。新增单元仅这一个出口函数+一枚常量+一个私有测试套件,无段件/状态件迁出。code-conventions §1.1 与本节互指;实现期若需拆 `camp_hud_band.gd` 或 `main.gd` 才落得下,属卡片未预见的结构动作=L3 呈报,禁为凑绿硬拆。 --- -## 15. 待裁点(复核产生,两条都合规、代价不同——设计权归用户) +## 15. 待裁点(三轮复核产生;Q1 因写卡段自决被驳回而重开过、又因出口的执行形态被再裁一次——全程留痕) -独立复核(未参与撰写的只读子代理)把本卡打回一次,四条闭合里三条由写卡段按权威文档自决落卡,第 4 条拆成两案呈用户裁决。**两案现已裁尽,本卡零待裁点,开工门=U0(用户批卡)。** 以下留档=各案备选、代价与裁决,供复核与编排段比对,不再重开。 +独立复核(未参与撰写的只读子代理)三轮:轮次 1 打回一次,四条闭合里三条由写卡段按权威文档自决落卡,第四条拆成两案呈用户裁决;轮次 2 抓到「矩形谁建」这条轴属越权自决;轮次 3 抓到收编版四处硬伤并生成 Q4。**当前状态:Q1/Q2/Q3 已裁尽,Q4 因 11-06 用户再裁而消解(不再是一条分叉),本卡零待裁点,开工门=U0(用户批卡)。** -### Q1|栏体这一面从哪来——**已裁(2026-11-05 用户裁「按放在公共区域的这一层」)** +### Q1|顶栏这块色面从哪来——**已裁(终态=公共件一个具名单参出口,同形旧实现一律不动)** -事实底座:`UiAssembler.overlay_bg(host, color)`(`game/presentation/ui_assembler.gd:224-230`)确实做了「ColorRect + PRESET_FULL_RECT + add_child + 返回句柄」这四步,语义就是「铺满宿主的色面」。但该文件 `extends RefCounted`、**无 `class_name`**,现网一律 `const UiAssembler := preload("res://presentation/ui_assembler.gd")` 引用(`main_ui_build.gd:7`、`scenes/adventure_screen.gd:21`、`scenes/menu_screen.gd:15`),**引用方全在组合根与屏壳侧,`views/` 目录零先例**;而 `views/` 内直接 `ColorRect.new()` 有先例(`views/battle_end_overlay.gd:261`/`:289`/`:314`)。 +事实底座:`ui_assembler.gd:224-230` 的 `overlay_bg()` 做了「新建 ColorRect+铺满锚点+挂上宿主+返回句柄」四步,**没设 mouse_filter**(ColorRect 默认 STOP),外部调用者零(只有该文件自身 `:100` 传 `BG_BRIGHT` 亮底、`:214` 传黑 0.6 遮罩);`ui_kit.gd:486-491` 的私有 `_modal_dim()` 是同一份东西的第二实现(同样不设 mouse_filter,仅 `:511` 一处用);`ui_kit.gd:537` 页壳底板同形但**显式**写 STOP、色为实色、后有石纹/暗角兄弟层;`views/` 目录自建 ColorRect 有 7 个文件 15 处先例;顶栏件 `camp_hud_band.gd:14` 现役已在调 `UiKit`。 -| 案 | 做法 | 代价 | -|---|---|---| -| 甲 | `camp_hud_band.gd` 加 `const UiAssembler := preload(...)`,调 `overlay_bg(self, Color(...))` | 复用现成件、零重复实现;但给 `views/` 开一条此前零先例的装配器引用路径(该文件头注自陈「接线拓扑唯一归组合根——本类零信号连接」,被视图件引用属越界用法) | -| 乙 | 视图内 `var band := ColorRect.new()` 四行自铺 | 与 `views/` 既有形态同构、零新路径;代价是重复装配器已有的四行(第 12 节要申报「为什么不能复用那一个」) | +当时并列过的案与代价:甲=顶栏件 preload 装配器复用那一个出口(零重复,但给 `views/` 开一条该目录零先例的引用路径,且该文件头注自陈「接线拓扑唯一归组合根」);乙=顶栏件自建(与 `views/` 现状同构,但重复四行,且色值与铺法分居两处);丙=公共件出一个出口(真「整件外观一处定义」,代价是 `ui_kit` 公开方法 23→24 要修订登记基线)。 -呈裁时并列三案(甲=视图引装配器、乙=视图内自建矩形、丙=提到公共件层);裁决过程中又查实一条规范原文——`ui_kit.gd:2` 文件头「全站取色/描边唯一出口——各视图禁内联 Color8/StyleBoxFlat 字面量,换皮只改本文件」,它把"色值放哪"与"矩形谁建"这两件事分开了。 - -> **裁决(2026-11-05 用户裁「按放在公共区域的这一层」)**:取**丙的取值面+乙的动作面**——色值与 α 做成公共件里的一枚派生常量 `UiKit.CAMP_HUD_BAND`(已实测:`Color(CAMP_HEADER_BAND, 0.80)` 编译通过,`to_html(true)`=`6e4526cc`,三分量与母 token 逐一相等),矩形由顶栏件自建。**甲淘汰**(引入一条零先例且无机器检查拦截的跨文件引用);**丙的"抽公共工厂"那一半不做**,理由与留痕句见第 12 节末。已落 T3.1、第 7 节结构图、第 12 节、契约4、第 13 节。**解释面报备**:轮次 2 复核指出裁决句字面只写「放在公共区域的这一层」,未逐字覆盖「矩形谁建」这条轴。写卡段按"该回话针对的是当轮给出的整条推荐(色值进公共件+矩形留在视图自建)"处置,故两半同批落卡;用户若不认这一读法,只需回一句,重开范围仅止于顶栏矩形的建造方,不动色值落点与其他结论。 +> **裁决史(四次)** +> ① **11-05**:用户裁「就按放在公共区域的这一层的方案来实现」。写卡段把它解释成"采纳我当轮给的整条推荐",落成**色值进公共件+矩形留在视图自建**,并在第 12 节写下"不抽公共工厂"的留痕句。 +> ② **11-06 用户驳回①并重裁**:驳回理由是那条解释本身——"值"和"形"是同一个 UI 物件的两半,拆两处就要求每个改它的人同时记得两处,而"少动文件"不是纪律。**当时落成的重裁结果=丙的整案**:公共件出一个 `band_surface(host, color, pass_input)` 通用出口封装铺面与点击语义,并把 `_modal_dim()`、`overlay_bg()` 连同三处调用点同批收编,据此生成 Q4。 +> ③ **11-06 写卡段据 Q4 呈「页壳底板收不收」**,用户判该设计整体不成立(原话:「你现在是有非常严重的设计问题」)——出口一旦需要形参承载三种点击语义,它就不再是「一个物件」而是三个意图的并集;会问出 Q4 本身就说明抽象切错了。 +> ④ **11-06 终裁(现行)**:出口**只服务营地顶栏这块带子**——具名 `camp_hud_band_surface(host)`,**形参只有 host**,色值、α、铺满、穿点击全写死在体内;`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`、`ui_kit.gd:862` 一律**不收编、不动**。已落契约4/5、§2、§3.3、§5、§7、§12、§14、T3.1–T3.3。 +> ⑤ 流程账:轮次 2 复核当时已把①那条轴标为**待裁**(原话:裁决句字面只覆盖色值轴),写卡段改成"自判+报备"自行闭合——那是越权(规矩:待裁必须问用户、禁代裁),第 16 节轮次 2 相应行按实情登记。 ### Q2|徽章「右位顶格+内缩 12px」用什么机制随位数自适应——**已裁(2026-11-05 用户裁乙·容器右对齐)** @@ -318,13 +325,17 @@ 写卡推荐=**乙**,理由=仓内已有同类摆位先例(容器 `alignment` 摆位,非手算偏移),且 R5 的 x 向判据在容器形态下读数最直白。甲依赖我不肯凭记忆断言的引擎语义(AGENTS.md:不确定的 API 一律核实后再用,核实前不作推荐)、丙新增运行期路径。 -> **裁决(2026-11-05 用户裁·乙 容器右对齐)**:已落 T3.2 与第 12 节,Q2 清零。 +> **裁决(2026-11-05 用户裁·乙 容器右对齐)**:已落 T3.3 与第 12 节,Q2 清零。 **Q1/Q2 与第三条(闸件缺口是否登记)都已裁完并落卡,本卡零待裁点,开工门=U0。** +### Q4|页壳底板要不要一并收进同一个出口——**已消解(11-06 用户终裁:不收编,出口只服务顶栏带)** + +留档(非现行分叉):`ui_kit.gd:537` 页壳底板与出口形状相同(新建 ColorRect、赋色、铺满、挂上宿主),差别是它**显式**写 `MOUSE_FILTER_STOP`、色取 `BG_BRIGHT`/`BG_ROOT` 实色,且后面紧跟石纹层与暗角层作兄弟节点——它是「页面底」,不是「遮罩/条带」。`ui_kit.gd:862`(竖卡 18×16 小色块)不同形(手工 position/size、不铺满)。轮次 3 把前者判为待裁并生成 Q4,呈报时并列「不收编(甲)/一并收编(乙)」两案。用户回话否定的是这条呈报的**前提**:出口本来就不该是通用铺面器,所以「第四处收不收」不成立。按终裁④,`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`、`ui_kit.gd:862` 四处全部原样不动。 + ### 其余复核发现的处置(已落卡,非待裁) -自判与补写两类共 15 条已按复核者自己落卡的规矩改进行内:契约1 收窄措辞、`_start_adventure()` 函数体行号订正、失败分支终态申报、帧口径、R6 输入来源改读实件、R8 行号与能力行拆正、结构图验收行补 R7/R8、悬空「行为表」引用、L1 引用行号、白名单第 4 条取样口径、`_initialize()` 入口名、新件三查对 tests 不适用、完成定义行八行、`main.gd:137` 与其余开层点不受契约1 约束的明示。(轮次 1 对 R3 给的「升成员供测试直取」处置已在轮次 2 撤回——它抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部,改判为公开子节点序判据。) +自判与补写两类已按复核者自己落卡的规矩改进行内:契约1 收窄措辞、`_start_adventure()` 函数体行号订正、失败分支终态申报、帧口径、R6 输入来源改读实件、R8 行号与能力行拆正、结构图验收行补 R7/R8、悬空「行为表」引用、L1 引用行号、白名单第 4 条取样口径、`_initialize()` 入口名、新件三查对 tests 不适用、`main.gd:137` 与其余开层点不受契约1 约束的明示、R3 child(2) 硬伤、R5 开营地前置与 await 时机、T3.0 入树读数时机、1280 宽承重链改引 `ui_registry.gd:26`/`:61`、徽章 x 向判据改全局矩形、显隐点 12→13、`views/` 先例 8 文件→7 文件 15 处、步骤号与 R 行口径重编号。(轮次 1 对 R3 给的「升成员供测试直取」处置已在轮次 2 撤回——它抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部,改判为公开子节点序判据。) 两条闸件面发现**均已登记为工程改动待办**(用户 2026-11-05 口径:登记后本批不管):认锚式字符类不含中文段(`tools/pipeline_gate.py:68`)、层序落位单口的扫描面不含子目录(`game/tests/unit/contracts/source_guard_test.gd:85-99`)。两条都在待办清单在册(改动面=工具代码/状态=待执行),本批不动闸件。 @@ -338,44 +349,90 @@ | 轮次 | 复核者身份 | 发现(一句话) | 去向 | 处置结果 | |---|---|---|---|---| -| 1 | 只读子代理·未参与撰写 | 契约1「隐藏点只许两处」按字面过宽——全库开始层显隐共 12 处 | 退回上游(复核者判)→ 落卡改自判 | 契约1 收窄为「限开局/续玩两条路径」,12 处逐条列名(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`)。复核者的「退回上游」定名不采纳:这是卡内条款措辞过宽,非规范冲突,写卡段可自改 | +| 1 | 只读子代理·未参与撰写 | 契约1「隐藏点只许两处」按字面过宽——全库开始层显隐共 13 处 | 退回上游(复核者判)→ 落卡改自判 | 契约1 收窄为「限开局/续玩两条路径」,13 处逐条列名(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`/`:101`)。复核者的「退回上游」定名不采纳:这是卡内条款措辞过宽,非规范冲突,写卡段可自改 | | 1 | 同上 | `_start_adventure()` 函数体行号错(写 311,实止 308) | 自判 | §3.1 改 `292-308` 并注明 `:311` 是下一函数声明行 | | 1 | 同上 | T1.1 落笔后启动失败分支的开始层终态由可见变隐藏,卡未申报 | 补写 | §3.1 该行补终态申报(致命屏自接管,无菜单可用) | | 1 | 同上 | 大门段 await 只 1 帧,选路停点成形先例是 3 帧 | 补写 | T1.4 补帧口径+最小帧数实测记进实现报告;引 `screen_flow_probe.gd:185-193` | -| 1 | 同上 | `btn_row` 是 `_init()` 局部变量,R3「逐位相等」写不出来 | 补写(阻塞) | T2.1 同批把 `btn_row` 升为成员 `_btn_row`;R3 绑定面按 test-conventions §8.2 受控例外申报理由 | -| 1 | 同上 | §12 自称「不直取私有」,R3/R4 实际直取私有成员 | 自判 | §12 该格与 R3 改为受控例外口径(位次无公开可观测面可钉),不再谎称未直取 | +| 1 | 同上 | `btn_row` 是 `_init()` 局部变量,R3「逐位相等」写不出来 | 补写(阻塞) | 当时把 `btn_row` 升为成员并申报受控例外;该处置在轮次 2 撤回(抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部),现行判据=公开子节点序,不新增成员 | +| 1 | 同上 | §12 自称「不直取私有」,R3/R4 实际直取私有成员 | 自判 | 同上:先改为受控例外口径,轮次 2 再改为完全不读私有成员(R3 走子节点序、R4 走坐标真点) | | 1 | 同上 | `:702` 真点已消耗 `gate_new`,R4 需重开营地+大门才有左位可点 | 补写 | T2.3 补「前置重开」步与坐标取法 | | 1 | 同上 | L1 引用的 `ui_click_probe.gd:679` 不是旧口径注释;段内直发面在 `:696`;「emit 不算真实入口」属 §8.3 而非契约7 | 自判+**部分不采信** | L1 改引 `:696` 的 `cancelled.emit()` 并注依据=test-conventions §8.3。**但复核者称 `:679` 是断点写盘句,经我回读不成立**——`:679` 确为段头注「大门两分支断言段」,断点注入起 `:706`;另 R8 原引 `:696` 亦错,次钮隐藏断言实为 `:694` | -| 1 | 同上 | `ui_assembler.gd` 无 `class_name`,现网一律 preload,`views/` 零先例;卡内 `UiAssembler.overlay_bg(...)` 写法不成立 | 待裁 | 第 15 节 Q1 全案并列(复用装配器/视图内自建),含两案代价与写卡推荐(乙);T3.1 改为「待 Q1 裁决后落」 | +| 1 | 同上 | `ui_assembler.gd` 无 `class_name`,现网一律 preload,`views/` 零先例;卡内 `UiAssembler.overlay_bg(...)` 写法不成立 | 待裁 | 第 15 节 Q1 全案并列(复用装配器/视图内自建/公共件出出口),含三案代价;T3.1 改为「待 Q1 裁决后落」 | | 1 | 同上 | 徽章自高 48 系推导,`panel_flat` 带 `border_w=1`,描边若计入则 50、R5 顶格判据即红 | 补写(复核者自标「这是推测,未跑批」) | T3.0 新增前置读数步(量 `get_combined_minimum_size().y`);≠48 走 L3 第 5 条;§3.3 与 R5 的推导句标为未实测 | -| 1 | 同上 | 12px 内缩无可达机制(徽章宽随位数变,现值写死 x 偏移);R5 只断 y 向,x 写错仍绿 | 待裁+补写 | 第 15 节 Q2 三案并列(锚点生长/容器右对齐/size_updated 回调);T3.2 改「待 Q2 裁决后落」;R5 已补 x 向判据(`end.x == 栏 end.x − 12`) | +| 1 | 同上 | 12px 内缩无可达机制(徽章宽随位数变,现值写死 x 偏移);R5 只断 y 向,x 写错仍绿 | 待裁+补写 | 第 15 节 Q2 三案并列(锚点生长/容器右对齐/size_updated 回调);T3.3 改「待 Q2 裁决后落」;R5 已补 x 向判据(`end.x == 栏 end.x − 12`) | | 1 | 同上 | R6 若把实现的 `Color(UiKit.CAMP_HEADER_BAND, 0.80)` 当输入=oracle 镜像 | 自判 | R6 改为规格侧算式核验(测试内按 `camp.md:41` 写死 `#6E4526`/α0.80),并明写「禁把视图件 color 喂进算式」,依据=test-conventions §8.7 | | 1 | 同上 | §11.4 称 4×8 步长「沿 `terrain_tile_sets_test` 口径」错,该件是全像素扫描 | 自判 | 白名单第 4 条改全像素双层扫描,并声明卡内早先步长口径作废 | | 1 | 同上 | 数学校核:全扫与抽样均色=RGB(223,235,232),与卡内 `#DEEAE7` 差 ≤1/255;叠后对比 4.29:1(卡 4.27)、徽章对天空 1.30:1(卡 1.29) | 自判(读数采纳,容差不改) | 落位带前提经其独立核对(背景层 `Rect2(0,0,1280,800)`、围墙 y=261、大门 y=250 → 0–48 带只含背景);差值落 L2 容差内,不放宽阈值 | -| 1 | 同上 | §1 完成定义与 §5 写「七行」,对照表实为 R1–R8 | 自判 | 完成定义改「八行(R1–R8)」 | +| 1 | 同上 | §1 完成定义与 §5 写「七行」,对照表实为 R1–R8 | 自判 | 完成定义改「八行(R1–R8)」(收编版曾扩到 R9,随该版作废回到八行) | | 1 | 同上 | §5 引「第 1 节行为表」,本卡无该表(悬空引用) | 自判 | 改为「七条未改动动线」自足表述 | | 1 | 同上 | §7 结构图 `ui_click_probe`/`system_test` 的验收行列缺 R7/R8 | 补写 | 结构图两行补齐 R1、R2、R4、R7、R8 / R5、R8 | | 1 | 同上 | gdstyle 只扫 core/data/presentation,tests 不在面 → §13 新件三查与 L3 第 4 条对新件无触发路径;套件入口是 `_initialize()` 非 `_init` | 自判 | §13 三查改「对本件不适用」并给依据(`tools/gdstyle_gate.py:45`+头注 `:8`);L3 第 4 条限定为 presentation 面三件;T2.2 入口名改 `_initialize()` 并附先例行号 | | 1 | 同上 | §13 存量一查核过无误(基线 32/23,`main.gd` 公开方法 31+1 静态=32 持平成立) | 自判(确认类) | 无需改动,留档为已核项 | | 1 | 同上 | 契约4 依据「加枚即触五 token 机检表」查无该件 | 自判+**不采信其结论** | 复核者称机检不存在,经我回读:`tools/palette_check.py:22` 的 `ui_tokens.png`=ui_kit 全量 Color8 token 快照基线,机制在——只是不逐名比对该表五行。契约4 依据改写成这个准确口径,仍禁新增 token | | 1 | 同上 | `run_all.gd` 册是硬编码清单,T2.2「接进套件册」不够具体 | 补写 | T2.2 写死条目形态 `{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}` +行尾注,并指 `run_all.gd:166` 起的清单 | -| 1 | 同上 | 总判:按现状不能进实现段,缺四块闭合 | 退回上游(部分) | 四块里①②④已自决落卡;③的 `UiAssembler` 形态与 12px 机制转 Q1/Q2 待裁——**本卡现为「待裁未清零」状态,不进实现段、不落 commit、状态不推「已写卡」**。另其归入的机检缺口(`pipeline_gate.py:68` `ANCHOR_RE` 不含中文段)已列 §6 末行,去向=用户发令登记 | +| 1 | 同上 | 总判:按现状不能进实现段,缺四块闭合 | 退回上游(部分) | 四块里①②④已自决落卡;③的 `UiAssembler` 形态与 12px 机制转 Q1/Q2 待裁。另其归入的机检缺口(`pipeline_gate.py:68` `ANCHOR_RE` 不含中文段)已列 §6 末行,去向=用户发令登记 | **写卡段对复核结论的两处不采信**(复核者也是读代码,不是跑批):`:679` 段头注的定性、五 token 机检「查无该件」——两处均经我逐行回读推翻,依据写在对应处置格。 -**复核报告落盘**:`reports/复核-营地交互修复批-2026-11-05.md`(轮次 1=22 条,轮次 2=5 条,同一文件续段登记)。 +**复核报告落盘**:`reports/复核-营地交互修复批-2026-11-05.md`(轮次 1=22 条,轮次 2=5 条,轮次 3=8 条,同一文件续段登记)。 -## 轮次 2(Q1/Q2 裁决落卡后的独立复核) +## 轮次 2(Q1/Q2 首次裁决落卡后的独立复核) 复核者身份=第二个未参与撰写的只读子代理(与轮次 1 不同一会话),任务书限定只查五处并须给 `文件:行号` 依据。 | 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 | |---|---|---|---|---| | 2 | 只读子代理·未参与撰写 | **硬伤**:双钮形态下断言「child(1)=维持」必红——次钮在 `open()` 里只置 `visible=false`、不从子节点表移除 | 补写 | R3 与 T2.2 改判 child(2),并配「按钮行子节点数=3」非空前置;依据 `confirm_dialog.gd:47` | -| 2 | 同上 | R5 缺前置:营地屏 boot 态为隐,未开营地读不到尺寸 | 补写 | R5 真实入口补「先 `open_camp()` + await 排版帧」;依据 `camp_scene_view.gd:51` | +| 2 | 同上 | R5 缺前置:营地屏 boot 态为隐,未开营地读不到尺寸 | 补写 | R5 真实入口补「先 `open_camp()` + await 排版帧」;依据 `camp_scene_view.gd:53` | | 2 | 同上 | T3.0 未写取数时机:PanelContainer 的最小尺寸在入树排版后才可信,刚 `new()` 读数不可信 | 补写 | T3.0 改为「入树后经公开句柄读在树的现成徽章实例,await 排版帧再读」;依据 `ui_kit.gd:634-656` | | 2 | 同上 | 确认类(无发现):八处行号全对(`main.gd:211-212`/`:223-224`/`:292-308`/`:194`/`:246`、`ui_kit.gd:640` 带 `border_w=1`、`ui_assembler.gd:100`/`:214`/`:224-230`、`ui_kit.gd:189/190/200`);`to_html(true)` 追加 α 两位、期望串 `6e4526cc` 对;尺寸链 1280×48 可达(CampScreen 满铺+`_hud_slot` 全宽,父链无容器改写);「按类型找第一个 HBoxContainer」不会找错(该树只有一个) | 自判(确认留档) | 无需改卡,核法记档:逐行打开比对+仓内既有用法 `standee_present_values_test.gd:36`(`to_html(false)` 六无 #) | -| 2 | 同上 | Q1 的裁决句「按放在公共区域的这一层」字面只覆盖色值落点;「矩形由顶栏件自建、不抽公共工厂」是写卡段对裁决的展开,该轴轮次 1 原属甲/乙案内容 | 待裁(复核者判)→ **按自判落并报备** | 依据对话原话序列:写卡段呈案时那句推荐是「色值进公共件、矩形留在顶栏件自建」(两半同批给出),用户回话为「那没有问题,就按放在公共区域的这一层的方案来实现」——回话指向的是该推荐整体而非仅色值半轴,故按已裁处置。该解释已写入第 15 节 Q1 裁决格供比对;**用户若不认这个解释,本条重开为待裁**,且重开只影响顶栏矩形的建造方,不动其余结论 | +| 2 | 同上 | Q1 的裁决句「按放在公共区域的这一层」字面只覆盖色值落点;「矩形由顶栏件自建、不抽公共工厂」是写卡段对裁决的展开,该轴轮次 1 原属甲/乙案内容 | **待裁(复核者判,事后证明它判对)** | 写卡段当时把去向改成"自判+报备"并自行闭合——**该改判无效**:规矩是待裁必须问用户、禁代裁。11-06 用户驳回拆两半的方案,落点见第 15 节 Q1 裁决史②与④;本行处置因此从"已闭合"改记为"越权闭合→已由用户重裁" | -**轮次 2 总判**:三处补写已全部落卡;一处确认留档;一处按自判落并向用户报备(可重开)。落卡后本卡零待裁点。 +**轮次 2 总判**:三处补写已落卡(现行有效);一处确认留档;一处按自判落并向用户报备(该自判后被用户驳回)。 + +## 轮次 3(11-06 收编版落卡后的独立复核,第三个未参与撰写的只读子代理) + +复核者身份=第三个只读子代理(与前两轮不同会话,禁写禁跑)。它抓到 6 项:4 项我照改、1 项它判对而我上一轮判断错、1 项转用户裁决。 + +| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 | +|---|---|---|---|---| +| 3 | 第三只读子代理 | 两处旧实现确实没设 mouse_filter、ColorRect 默认即 STOP——出口默认值取 STOP 保旧语义的做法成立 | 自判(确认) | 该事实现行有效,已并入契约5 作「漏设不报错」的旁证(`:540` 显式 STOP、`:864`/`camp_scene_view.gd:145` 显式 IGNORE);「出口默认取 STOP」这一设计随收编版作废 | +| 3 | 同上 | 三处既有调用点都是"挂宿主+把返回句柄当父用",收编为转调在层序上等值 | 自判(确认) | 随收编版作废(11-06 终裁不收编),留档 | +| 3 | 同上 | `_modal_dim()` 经 `modal_shell()` 扇出 **11 家**模态消费件,R9 只点两处探针不足以覆盖 | 补写(收编版内部) | 随收编版作废;它揭示的事实(该私有件扇出面广)现作为**不收编的理由**并入契约4 与 §5「不做」表 | +| 3 | 同上 | **卡内 1280 宽的承重链引错**:宽度不是 tscn 锚点给的,是 `ui_registry.gd:26`/`:61` 的 FULL_RECT 注册撑出;R5 第⑤条还把全局矩形与局部 `position` 混写 | 补写(且推翻我自己) | **现行有效**:R5 改引注册面并把⑤改为 `get_global_rect()`。**同一条我上一轮"纠正"错过**:我曾判 `camp_scene_view.gd` 初始隐在 `:51` 并把复核者的 `:53` 改回去,本轮 grep 复核实为 `:53`,已全文改回 | +| 3 | 同上 | R9/T3.2 的收编完成判据算术不成立:`ui_kit.gd` 现有 3 处 ColorRect 自建(`:487`/`:537`/`:862`),"两文件合计=1"必红 | 补写(收编版内部) | 该判据随收编版作废;它抓到的事实(同形写法实为三处,不止两处)保留,现行 §3.3/§12 按三处陈述 | +| 3 | 同上 | `:537` 页壳底板与出口同形(铺满+纯色),仅显式 STOP 与色不同——形状同而语义是否同,判为待裁 | **待裁(转用户)** | 当时列进第 15 节 Q4;用户 11-06 回话否定该呈报的前提(出口不该是通用铺面器),Q4 消解,见 Q1 裁决史③④ | +| 3 | 同上 | 卡内多处数字与交叉引用过期:显隐点 12→**13**(漏 `main_overlay_flow.gd:101`)、`views/` 自建先例 8 文件→**7 文件 15 处**、§5 写 R1–R8 而 §1 写 R1–R9、§12 三处步骤号、T4.1 目检引用、§14 与 §15 把 tooltip 底算进收编用途(`TIP_BG:190` 是主题色常量,不经这两个函数) | 补写 | 数字类改正(13 处、7 文件 15 处)现行有效;tooltip 底不属于任何用途清单,现行 §3.3 按「亮底/黑 0.6 遮罩/页底/顶栏带」四种意图分述;步骤号与 R 行口径随本轮撤收编重编号一并落定 | + +**写卡段对轮次 3 的不采信项**:无(它唯一被我推翻的是我自己上一轮写错的 `:53`,本轮已按它的正确值改回)。 + +**轮次 3 之后的改判**:轮次 3 审的是「收编版」;11-06 用户终裁(Q1 裁决史④)撤掉收编与带形参的通用出口,本卡据此重落成「一个具名单参出口」——该终态须经一轮新的独立复核才算闭合,遂有轮次 4。 + +## 轮次 4(11-06 撤收编版落卡后的独立复核,第四个未参与撰写的只读子代理) + +复核者身份=第四个只读子代理(与前三轮不同会话,禁写禁跑,任务书要求逐条给 `文件:行号`)。它报 17 条:2 条硬伤、4 条事实错/一致性问题、2 条补写、5 条确认无发现;**未新增待裁点**(无需用户裁决)。发现与处置: + +| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 | +|---|---|---|---|---| +| 4 | 第四只读子代理 | **硬伤**:R4 字面「按按钮行实际矩形算左位坐标真点」必打空——该行 `alignment=CENTER`,双钮可见宽 216+12+216=444<壳宽 460,行矩形左位落在 8px 空档上 | 补写 | R4 真实入口改为取按钮行 child(0) 的 `get_global_rect()` 中心,并把算式与依据行号写进格内(`confirm_dialog.gd:29`、`ui_kit.gd:578` `min_w=216.0`、`confirm_dialog.gd:20` 壳宽 460)——**我回读三行确认成立**;T2.3 原本就写的就是 child(0) 中心,两处口径现已统一 | +| 4 | 同上 | **硬伤**:T3.4 让 `_smoke_camp_hud_band()` 「接进 `_initialize()`」不成立——`_initialize()` 只调 `_split_tail()`,摘要与 `quit()` 都在尾段,不被 await 的协程段=静默不执行=R5 假绿 | 补写 | T3.4/§7 结构图/§11 第 5 条三处同批改为「接进 `_split_tail()` 的 await 链(`:40-44`)」并写明失败机理与段内自持 instantiate/queue_free 的先例形态——**我回读 `system_test.gd:26-28`/`:35-48`/`:213-217` 确认成立**,`:40` 行尾注本身就记着这条教训 | +| 4 | 同上 | 四消费方行号偏一:卡写 `:72`/`:76-78`/`:82`,实为 `:73`/`:79-80`/`:83`(`:129` 对) | 自判 | §3.2 与 R3 两处改正——我逐行打开 `main_overlay_flow.gd:69-83`/`:125-129` 核实 | +| 4 | 同上 | R5② 把「营地场景根代码 new」引到 `camp_scene_view.gd:13-14`,该行实为命中区名表 `_HOTSPOT_NAMES`;真正的 new 在 `scenes/camp_screen.gd:13` | 自判 | R5② 改引 `game/presentation/scenes/camp_screen.gd:13-14` 并补 `camp_screen.tscn` 根锚 preset=15 的承重事实——我回读两文件确认 | +| 4 | 同上 | R3 括注「`modal_shell()` 产 VBox+HSeparator」不实:HSeparator 是弹窗自造(`confirm_dialog.gd:26`),该壳不产横排 | 自判 | R3 括注改为「产 CenterContainer+PanelContainer+VBox,不产横排;HSeparator 系弹窗自加」——回读 `confirm_dialog.gd:26` 与 `ui_kit.gd:509-525` 确认 | +| 4 | 同上 | T3.3/§12 称 `confirm_dialog.gd:27-29` 为「容器右对齐先例」,而 `:29` 实为 `ALIGNMENT_CENTER`——是同机制先例、不是右对齐先例 | 自判 | 两处措辞改正,并明写本件同处取 `ALIGNMENT_END` 才是右对齐(免得实现误认现成右对齐件) | +| 4 | 同上 | R6/T3.5 引「§8.4」未冠文档名,卡内 §8 无 8.4 小节,易读作自引 | 自判 | 改为「test-conventions §8.4(正面效力自检)」 | +| 4 | 同上 | R4 判据读 `main._confirm_consumer`(私有),与 §12「不读私有成员」的自陈口径不一;但探针面先例在案、封装扫描件射程不含探针 | 自判 | R4 明写主证=`run_scope.is_adventure` 翻转,私有一读注为探针既有先例(`ui_click_probe.gd:272`/`:286` 现役直读),并记扫描射程(`presentation_encapsulation_sweep_test.gd:80-94` 的 `root\._` 规则只套在五个 flow 件上,`main.gd` 那格取 false)——**其路径写法有误**(该件在 `game/tests/unit/presentation/`,不在 `unit/contracts/`),结论经我回读不受影响 | +| 4 | 同上 | §14 出口职责句「返回句柄供顶栏件摆放徽章」与 T3.2 实际丢弃返回值、摆位归容器不符 | 自判 | 职责句改为「供调用方读取与断言」,并明写摆位不由该出口负责(归 T3.3 的容器) | +| 4 | 同上 | T3.0 只把「徽章自高≠48」归因于描边 1px,漏了第二条成因:徽章内 Label 取 18px 档(`ui_kit.gd:648`→`:438` `FS_SUB=18`),行高若高于 32px 图标则最小高由文字决定 | 补写 | T3.0 与 L3 第 5 条两处成因并列;处置强度不变(实测+≠48 即停机呈报)——回读 `ui_kit.gd:638-656` 确认该 Label 用 FS_SUB | +| 4 | 同上 | R2 的判据(`is_adventure`+`level_screen.visible`)不是件1 改动特有的面,改了也可能不红 | 补写 | R2 格内明写「本行是开局终态在位断言,被测改动的特有提交面由 R1 承载;`is_adventure` 既有探针已断(`ui_click_probe.gd:704`)」,两行同批缺一即红 | +| 4 | 同上 | 待办清单本条实况仍写「两轮独立复核报告」,现为三轮+本轮 | 补写 | 待办清单实况行随本轮改卡同批更新(写卡段 owns 该条目状态与实况,非实现批动作) | +| 4 | 同上 | 收编版残留扫描:无一处把收编/通用出口/color·pass_input 形参/R9/改 `ui_assembler` 写成现行方案;历史叙述均带作废标记;T3.0–T3.7、R1–R8、白名单 1–6、L3 1–5、裁决史①–⑤ 交叉引用零悬空 | 自判(确认留档) | 无需改;核法=全文逐节比对+按符号 grep 残留 | +| 4 | 同上 | 行号核对:40 处引用逐行打开无误(`ui_kit.gd` 17 处、`camp_hud_band.gd` 6 处、`confirm_dialog.gd` 6 处、`camp_scene_view.gd` 5 处、`main.gd` 8 处、presenter 2 处、装配链与注册面、两份基线脚本、五份文档锚点) | 自判(确认留档) | 无需改——注意该「无误」不含本表第 3、4 两条(它们正是被推翻的例外) | +| 4 | 同上 | 引擎语义:未入树节点调 `set_anchors_preset(FULL_RECT)` 的四边 offset 归零、铺满宿主,与现役 `_modal_dim()`/`overlay_bg()` 同形,R5② 的 1280×48 成立;顶栏件 1280 宽由 `ui_registry.gd:61` 的 FULL_RECT 注册撑出 | 自判(确认) | 无需改判据;按建议在 T3.4 补一句「出口须在 `_init()`(入树前)调用」的时机理由 | +| 4 | 同上 | `Color(常量, α)` 的 const 派生在库内大量现役;const 引用同文件 const 的写法 grep 零先例,卡内凭 11-06 临时工程实测(仓库无产物可复验) | 自判(确认,标风险) | 维持卡内实测句,不另设断言;该写法若失配属编译期 fail-fast,不会静默绿 | +| 4 | 同上 | 契约面与外部一致性:契约4/契约5/§5 不做行三者无矛盾无空隙;`game/tests/README.md` 两行新增能力名与 §8 能力行列逐字一致;§13 预算实算正确(`ui_kit` 29 个 static func 减 6 个私有=23→24;`main.gd` 32 持平;tests 不在 gdstyle 射程;palette 只匹 `Color8(`) | 自判(确认留档) | 无需改 | + +**写卡段对轮次 4 的不采信项**:1 条——它把 `presentation_encapsulation_sweep_test.gd` 记在 `game/tests/unit/contracts/` 下,实际在 `game/tests/unit/presentation/`;该错的只是路径,其结论(扫描射程不含探针、`root._` 规则只套 flow 件)经我回读 `:80-94` 成立,已按正确路径写进 R4。 + +**轮次 4 总判与现状态**:两条硬伤与四处事实错已全部落卡,四条一致性问题与三条补写同批落定,未新增待裁点。**本卡的终态设计(一个具名单参出口、零收编)至此经第四轮独立复核并闭合,零待裁点,开工门=U0(用户批卡)。** diff --git a/待办清单.md b/待办清单.md index 90da9a00..ece3ba82 100644 --- a/待办清单.md +++ b/待办清单.md @@ -27,7 +27,7 @@ **实况**:三件同批一张需求卡片,对照表内一件一行。 - 需求卡片 reports/需求卡片-营地交互修复批-2026-11-05.md -- 两轮独立复核报告 reports/复核-营地交互修复批-2026-11-05.md +- 四轮独立复核报告 reports/复核-营地交互修复批-2026-11-05.md - 设计定稿 design/gdd/ux/camp.md v2.8 §顶栏、design/gdd/systems/表现与支撑/HUD框架.md §提示与弹层、design/art/art-bible.md v3.43 §6.2 - bug 证据①被开始层盖住:`camp_shop_presenter.gd:85` 显层 → `main.gd:211-212`/`223-224` 先关营地再开局 → `main.gd:292` 不隐藏;该动线测试与探针零覆盖 - bug 证据②否左是右:`views/confirm_dialog.gd:31-39` 加入序=取消→次→确认 From 767920827990c2404d566b88c7050346278b3e06 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 05:44:55 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=961/7=C2=B7ui=5Finput=5Fprobe):=20=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E9=9D=A2=E6=9D=BF=E5=85=B3=E9=97=AD=E9=92=AE=20emit?= =?UTF-8?q?=E2=86=92=E7=9C=9F=E8=BE=93=E5=85=A5(push=5Fclick+release?= =?UTF-8?q?=E6=B2=BF,p16=E5=B1=80=E9=83=A8=E5=8F=98=E9=87=8F)=E2=80=94?= =?UTF-8?q?=E2=80=94case=E2=91=AF=E5=85=B3=E9=97=AD=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=E6=94=B9=E7=BB=8F=E8=BE=93=E5=85=A5=E7=AE=A1?= =?UTF-8?q?=E7=BA=BF;=20=E5=90=8C=E6=96=87=E4=BB=B6=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B011=E6=9D=A1=E8=90=BDcase?= =?UTF-8?q?=E6=AE=B5(=E2=91=A0-=E2=91=A2=E2=86=92HUD=E6=A1=86=E6=9E=B6:269?= =?UTF-8?q?=E6=82=AC=E5=81=9C=E8=AF=AD=E4=B9=89/=E2=91=A3=E2=91=A5?= =?UTF-8?q?=E2=86=92=E6=8E=92=E9=9B=B7=E7=8E=A9=E6=B3=95:231=E5=B0=81?= =?UTF-8?q?=E5=8D=B0=E6=8C=96=E6=8E=98=E5=8F=97=E7=90=86/=E2=91=A4?= =?UTF-8?q?=E2=86=92:236=E6=8B=92=E7=BB=9D=E4=B8=89=E6=BA=90/=E2=91=A6-?= =?UTF-8?q?=E2=91=A9=E2=86=92battle.md:154=E4=B8=BB=E8=A7=92=E7=AB=99?= =?UTF-8?q?=E6=A1=A9=E8=90=BD=E5=BD=B1/=E2=91=AA=E2=86=92HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6:264=E6=95=B0=E5=AD=97=E5=8F=AF=E8=AF=BB/=E2=91=AD?= =?UTF-8?q?=E2=86=92=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82:154=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=B7=AF=E7=94=B1/=E2=91=AE=E2=86=92HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6:268=E8=BF=B7=E9=9B=BE=E4=B8=A4=E6=80=81+=E5=85=83?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6:156=E8=A3=85=E9=85=8D=E5=8F=AF=E8=A7=81/?= =?UTF-8?q?=E2=91=AF=E2=86=92HUD=E6=A1=86=E6=9E=B6:277=E6=A8=A1=E6=80=81?= =?UTF-8?q?=E9=94=81/=E2=91=B0=E2=86=92HUD=E6=A1=86=E6=9E=B6:267=E5=BE=AE?= =?UTF-8?q?=E5=85=89=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F);=20=E6=9C=AC?= =?UTF-8?q?=E6=AD=A5=E5=87=AD=E6=8D=AE:=20ui=5Finput=5Fprobe=20headless?= =?UTF-8?q?=E7=9B=B4=E8=B7=91PASS=20exit=3D0(18=E6=96=AD=E8=A8=80=E7=BB=84?= =?UTF-8?q?=E5=85=A8=E7=BB=BF=E5=90=ABcase=E2=91=AF=E7=9C=9F=E7=82=B9?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=81=A2=E5=A4=8D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/ui_input_probe.gd | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/game/probe/ui_input_probe.gd b/game/probe/ui_input_probe.gd index e3ecde7c..968124c5 100644 --- a/game/probe/ui_input_probe.gd +++ b/game/probe/ui_input_probe.gd @@ -242,6 +242,7 @@ func _ready() -> void: _pre_fail("目标格选取失败 seal=%s dig=%s forbid=%s reveal=%s" % [seal_t, dig_t, forbid_t, reveal_t]) return # ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)---- + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:269 悬停光标三语义随目标切换、已开格路径预览、可挖域常驻微光(case①-③ 输入面) _push_motion(_pos(bv, seal_t)) await get_tree().process_frame if bv.hover_probe() != seal_t: @@ -270,6 +271,7 @@ func _ready() -> void: if bv.mouse_default_cursor_shape != Input.CURSOR_ARROW: _problems.append("case③ 移动光标缺失(%d)" % bv.mouse_default_cursor_shape) # ---- case④ 右键封印:seal_place 到达组合根 + 核心封印态落位 ---- + # oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 封印受理与印记随挖消失(case④/④b) _push_click(_pos(bv, seal_t), MOUSE_BUTTON_RIGHT) await get_tree().process_frame if not _has_action(seal_t, "seal_place"): @@ -283,12 +285,14 @@ func _ready() -> void: if seal_unit == null or not seal_unit.seal_on: _problems.append("case④b 封印印记未下发(seal_on=%s)" % ["无单元" if seal_unit == null else str(seal_unit.seal_on)]) # ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ---- + # oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 拒绝三源可辨、任何拒绝零副作用(case⑤ chord) _push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT) _push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT) await get_tree().process_frame if _chords.is_empty() or _chords[_chords.size() - 1] != forbid_t: _problems.append("case⑤ chord_attempted 未达(chords=%s)" % [str(_chords)]) # ---- case⑥ 左键挖:dig 到达组合根 + 核心揭格落位 ---- + # oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 挖掘受理与揭格落位(case⑥) _push_click(_pos(bv, dig_t), MOUSE_BUTTON_LEFT) await get_tree().process_frame if not _has_action(dig_t, "dig"): @@ -297,6 +301,7 @@ func _ready() -> void: if not ms.is_revealed(dig_t): _problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t) # ---- case⑦ 英雄精灵世界落位(回归闸):精灵在 SubViewport 世界内, + # oracle: design/gdd/ux/battle.md:154 主角站桩立绘带落影、贴脚与缩放档(case⑦-⑩ 呈现落位) # 落位 = 格×步距 + 脚底锚(横=格中线/纵=格底往上 25% 线=HERO_GROUND_FRAC 自底边口径——「脚踩 # 格子 1/4 处·从下往上数」;禁带壳层 _svp_container.position 居中偏移——上批误用壳层 # cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null,字形 @@ -363,6 +368,7 @@ func _ready() -> void: if bv._world.hero.position.distance_to(expect10b) > 0.5: _problems.append("case⑩ 还原后英雄未复位(pos=%s expect=%s)" % [str(bv._world.hero.position), str(expect10b)]) # ---- case⑪ 贴图缺位回退(字形「人」回归闸):texture 置 null + refresh → 英雄格数字 0 时字形 + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 棋盘格子与数字完整可见——贴图缺位回退字形保可读(case⑪) # 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ---- if bv._world.hero != null: var hp11: Vector2i = ms.hero_pos() @@ -379,6 +385,7 @@ func _ready() -> void: bv._hero_sprite.tex_idle = tex_saved bv.refresh() # ---- case⑭ 变异角标(flat_damage 未开格视觉标记;resolver 注入面+落位+复位) ---- + # oracle: design/gdd/systems/排雷循环/地形变异.md:154 呈现按 effect/trigger/placement 数据路由、零特判(case⑭ 角标) var mark_t: Vector2i = _pick_forbidden(bv, ms) # 任一未开格(禁入=未开非相邻) if mark_t.x >= 0: bv.set_variant_mark_resolver(func(_c): return "flat_damage") @@ -392,6 +399,7 @@ func _ready() -> void: else: _problems.append("case⑭ 无未开格可验(板面异常)") # ---- case⑮ 视野迷雾(三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位; + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:268 视野迷雾:灯光区随移动跟随、开关两态结算逐位一致(case⑮) # 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ---- var fogp: Dictionary = bv.fog_params_probe() if int(bv.FOG_RADIUS) != 8 or int(fogp["lantern"]) != 0 or not fogp["enabled"]: @@ -487,6 +495,7 @@ func _ready() -> void: # attune_param_sum 只读出口→set_fog_glow_bonus〕→视野 +4 生效与卸装复原。 # settlement.attunement 手动注入=模拟 begin_battle enc 注入后的运行态〔注入面本体归 # 冒烟+单测覆盖〕;不重启冒险——case⑯ 起既有板面继续用) + # oracle: design/gdd/systems/跨局循环/元进度.md:156 解锁可见:装配强化下一局生效(case⑮ 装配链) main.save_manager.meta_state.set_attunement_equipped(["wide_scout_band"]) var p_lantern: Dictionary = main.build_adventure_params(main.save_manager, main.meta) if not (p_lantern.get("attunement", []) as Array).has("wide_scout_band"): @@ -505,6 +514,7 @@ func _ready() -> void: _problems.append("case⑮ 卸装后 dist==R+1 格未复原遮蔽") main.save_manager.meta_state.set_attunement_equipped([]) # ---- case⑯ 状态面板输入锁语义(R3——开层锁棋盘、关层恢复无残留;真实 push_input + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开输入锁定、关闭逐层恢复(case⑯ R3) # 路径:入口角标本体 press+release 真点击 → 状态面板开;锁期挖掘点击零穿透;「继续」关层后 # 同格挖掘重新到达——锁语义与既有模态同款,无状态残留。置于全 case 之尾:取格若需前沿移位 #(真实点击 move 扩大可挖域),其板面扰动不再波及任何既有断言——域恰 2 枚时 seal_t 带封印+ @@ -547,7 +557,9 @@ func _ready() -> void: if resume_btn == null: _problems.append("case⑯ 状态面板关闭钮缺失") else: - resume_btn.pressed.emit() + var rpos16: Vector2 = resume_btn.get_global_rect().get_center() + _push_click(rpos16, MOUSE_BUTTON_LEFT) + _push_release(rpos16, MOUSE_BUTTON_LEFT) # Button pressed 信号走 release 沿——按压+释放成对 await get_tree().process_frame if main.ui.modals.run.status_panel.visible or main.ui.flow.locks_input(): _problems.append("case⑯ 关闭后未恢复(visible=%s locked=%s——R3 残留)" @@ -558,6 +570,7 @@ func _ready() -> void: if not _has_action(status_dig, "dig") or not ms.is_revealed(status_dig): _problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)") # ---- case⑰ 未开格类型微光(活板面走查:作用集恒等/开格灭/移动复亮 + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:267 类型微光出圈即灭入圈即亮、已开与封锁格恒无微光(case⑰) # 与出圈灭/相位门控/开关两态零扰动。与冒烟段数据面断言分工:此处走真实点击路径) ---- if not bool(bv.type_glow_probe().get("enabled", false)): _problems.append("case⑰ 微光默认态非开(生产默认开——Q4 丙案)") From 00a0b82a0aa7169f212f3f1efd0978c269982e13 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 05:49:37 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=962/7=C2=B7ui=5Fprobe):=205=E5=A4=84emit?= =?UTF-8?q?=E2=86=92=E7=9C=9F=E8=BE=93=E5=85=A5=E2=80=94=E2=80=94=E6=A0=91?= =?UTF-8?q?/=E7=A7=98=E5=AE=9D=E5=BC=80=E7=AA=97=3D=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=E5=8C=BApush=5Fclick=5Fat=E7=83=AD=E7=82=B9?= =?UTF-8?q?=E4=B8=AD=E5=BF=83,=E5=85=B3=E7=AA=97=3D=E7=9C=9F=E7=82=B9?= =?UTF-8?q?=E5=BA=95=E6=A0=8F=E8=BF=94=E5=9B=9E=E8=90=A5=E5=9C=B0=E9=92=AE?= =?UTF-8?q?(camp=5Fsub=5Fview=5Fbase:66-84=20closed=E5=8D=95=E6=BA=90),?= =?UTF-8?q?=E6=94=BE=E5=BC=83=E6=8B=92=E6=8C=89=3D=E7=9C=9F=E7=82=B9abando?= =?UTF-8?q?n=5Fbtn;=20=E5=90=8C=E6=96=87=E4=BB=B6=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B013=E6=9D=A1(level-map:59/11?= =?UTF-8?q?/42=E6=94=BE=E5=BC=83=E4=B8=8E=E5=85=A5=E5=8F=A3=E5=81=9C?= =?UTF-8?q?=E7=82=B9+camp.md:45/20/41/18=E7=AA=97=E5=A3=B3=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=E5=85=A5=E5=8F=A3+battle.md:122=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E7=BB=84+=E8=A1=8C=E5=8A=A8=E7=BB=8F=E6=B5=8E:258+HUD=E6=A1=86?= =?UTF-8?q?=E6=9E=B6:275/274=E7=AB=99=E6=A1=A9tooltip=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E9=94=AE+=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82:156+settlement-s?= =?UTF-8?q?ummary:20);=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20ui=5Fprob?= =?UTF-8?q?e=20headless=E7=9B=B4=E8=B7=91PASS=20exit=3D0(cells=3D144=20boa?= =?UTF-8?q?rd=3D12x12=20ap=5Fdots=3D8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/ui_probe.gd | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/game/probe/ui_probe.gd b/game/probe/ui_probe.gd index b106ba01..8a37e1d3 100644 --- a/game/probe/ui_probe.gd +++ b/game/probe/ui_probe.gd @@ -16,6 +16,7 @@ func _ready() -> void: await get_tree().process_frame var problems: Array = [] # ---- 放弃入口(台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)---- + # oracle: design/gdd/ux/level-map.md:59 放弃冒险入口=边缘动作、任意选路停点承载——非冒险模式无入口(默认隐藏) if main.ui.modals.run.level_screen.abandon_btn == null or main.ui.modals.run.level_screen.abandon_btn.visible: print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)") get_tree().quit(1) @@ -23,6 +24,7 @@ func _ready() -> void: # ---- 营地覆盖层(主菜单入口/开合/余额文案/购买禁用态; # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json——SaveManager 路径注入纪律; # 营地先行段只换 meta 不换 save_manager——冒险段换注零档在前)---- + # oracle: design/gdd/ux/camp.md:45 五功能窗统一窗壳、五窗互斥单开、各窗自带关闭钮(营地开合组) ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", false, false, false) if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_probe_camp"): problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)〔句柄子容器批 V1〕") @@ -34,10 +36,12 @@ func _ready() -> void: problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)〔句柄子容器批 V1〕") else: # 天赋树子界面(场景点选真实路由——木桩命中区进树界面;卡④ T5 深员改指) - main.ui.modals.camp.scene.object_pressed.emit(&"dummy") + # oracle: design/gdd/ux/camp.md:20 木桩命中区→天赋树界面入口(场景点选路由) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) await get_tree().process_frame if not main.ui.modals.camp.tree.visible: problems.append("天赋树子界面未打开(场景点选路由中断)〔营地图批 V2〕") + # oracle: design/gdd/ux/camp.md:41 钥石余额常驻画面顶部 HUD 栏(余额文案组) if not String(main.ui.modals.camp.tree._balance_label.text).begins_with("钥石"): problems.append("营地余额文案缺失(%s)〔句柄子容器批 V1〕" % main.ui.modals.camp.tree._balance_label.text) if main.ui.modals.camp.tree._tree_buy_btns.is_empty(): @@ -50,14 +54,15 @@ func _ready() -> void: problems.append("脊柱生命 L1 白板可购钮未启用(无门槛——can_buy 不含余额面语义)〔句柄子容器批 V1〕") if not btn_by_id.has("att_socket2") or not bool(btn_by_id["att_socket2"].disabled): problems.append("同调位 2 前置未亮应禁用(点亮前置语义)〔句柄子容器批 V1〕") - main.ui.modals.camp.tree.closed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")) # 真点底栏返回钮→_emit_closed(camp_sub_view_base.gd:66-84) await get_tree().process_frame # 秘宝匣子界面(场景点选真实路由——宝箱命中区) - main.ui.modals.camp.scene.object_pressed.emit(&"chest") + # oracle: design/gdd/ux/camp.md:18 宝箱命中区→秘宝匣界面入口 + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center()) await get_tree().process_frame if main.ui.modals.camp.treasure._workshop_rows.size() != 12: problems.append("秘宝匣行数错误(%d ≠ 12——迭代 22 秘宝批换代)〔句柄子容器批 V1〕" % main.ui.modals.camp.treasure._workshop_rows.size()) - main.ui.modals.camp.treasure.closed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")) await get_tree().process_frame main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3) await get_tree().process_frame @@ -71,6 +76,7 @@ func _ready() -> void: await get_tree().process_frame # ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层; # press_candidate 图上命中路径 pick 入口单候选进首战)---- + # oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言) # ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)---- if not main.ui.modals.run.level_screen.visible: problems.append("关卡开始未弹入口选路停点(level_screen 应可见)") @@ -86,14 +92,16 @@ func _ready() -> void: if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible: problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)") # ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)---- + # oracle: design/gdd/ux/level-map.md:42 放弃冒险(任意选路停点)→ 冒险落幕——战斗域单事务在飞时拒绝 if main.ui.modals.run.level_screen.abandon_btn == null or not main.ui.modals.run.level_screen.abandon_btn.visible: problems.append("冒险模式放弃按钮未开放(选路停点入口在案)") else: - main.ui.modals.run.level_screen.abandon_btn.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.modals.run.level_screen.abandon_btn) await get_tree().process_frame if main.run_scope.adventure == null or String(main.run_scope.adventure.state) != "running": problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)") # ---- 既有断言(回归基线;A 区无进度条——节点链进度由关卡画面独立屏承载)---- + # oracle: design/gdd/ux/battle.md:122 B 区构建组:主角与敌人站桩、明细带挖掘期收起、轮次指示、棋盘与 C 区/秘宝槽、首战进行中 if main.ui.battle.board_zone.board_view.cells_probe().is_empty(): problems.append("棋盘格未生成(cells=0)") if main.ui.battle.board_zone.board_view.board() == null: @@ -110,7 +118,8 @@ func _ready() -> void: if main.ui.battle.stage.enemy_box.get_child_count() < 1: problems.append("B 区敌方站桩未构建(%d 个)" % main.ui.battle.stage.enemy_box.get_child_count()) if main.ui.battle.board_zone.ap_bar._dots.get_child_count() != int(main.db.run.action_economy.ap_per_round): - problems.append("D 区 AP 点阵数量错误(%d)" % main.ui.battle.board_zone.ap_bar._dots.get_child_count()) + problems.append("D 区 AP 点阵数量错误(%d)" % main.ui.battle.board_zone.ap_bar._dots.get_child_count()) + # oracle: design/gdd/systems/排雷循环/行动经济.md:258 开轮硬重置:行动点回到表值(AP 点阵数=ap_per_round) if main.ui.battle.board_zone.ap_bar.refresh_remaining_text() == "": problems.append("D 区弃图刷新按钮文案缺失") if main.ui.battle.board_zone.toast == null: @@ -127,6 +136,7 @@ func _ready() -> void: # E 区无道具面板——四区构建由其余断言覆盖 # ---- 敌方站桩投影(顺位徽标/意图三档动作动画占位——敌人系统×4,全站桩)---- + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:275 敌人呈现:意图占位三档可见性——普通攻击零表示、类型级提示(站桩投影组) # B 区站桩子件伴随件化(壳层 tooltip 载体)——断言口径见台账 171: # 伴随件化(壳层 tooltip 载体),穿透明(子件 _name_label/_armor_icon/_intent_badge)改本体 # 探针出口 stage.enemy_body_at(i).name_line_probe()/intent_probe()——呈现口径逐字保持 @@ -153,6 +163,7 @@ func _ready() -> void: elif not bool(main.ui.battle.stage.hero_standee.portrait_probe().get("has_tex", false)): problems.append("主角立绘纹理未落位(显示档 hero=144——显示档批)") # ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)---- + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 状态槽图标+层数+剩余次数、悬停见来源(tooltip 回归组) var slots_row = main.ui.battle.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源) var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1, "remaining_settlements": 1, "source_class": "safe_cell", "round_index": 1, "effective_value": 50}] @@ -178,6 +189,7 @@ func _ready() -> void: # ---- 图标键回归(HUD框架 图标键契约:状态表九行全映射〔定稿 PNG 纹理〕、 # 格面资源 cell_type 九型全映射〔含 gp 键〕;查不到的键 fail-fast + ? 占位 # ——此处钉死映射不残缺,未知键断言由 _icon_tex/_badge_glyph 承担)---- + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 极性按键级表呈现——状态图标键映射不残缺(图标键回归组) for eid in ["attack_up", "ac_up", "alert", "weaken", "enrage", "off_guard", "sapped", "empower", "fortify"]: if not StatusSlotRow.EFFECT_ICON.has(eid) or StatusSlotRow.EFFECT_ICON[eid] == null: problems.append("状态图标九键纹理映射缺失(%s)" % eid) @@ -202,6 +214,7 @@ func _ready() -> void: # ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)---- # 单场无用户入口——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线; # _reset_run_scope 复现 _start_single 的提示去重复位) + # oracle: design/gdd/systems/排雷循环/地形变异.md:156 布置域:变异格全在初始展开区外(L2 布置域组) main.begin_session(false) main._debug.start_single(2) await get_tree().process_frame @@ -224,6 +237,7 @@ func _ready() -> void: # ---- GP 终局行:summary gp_final 键取数+胜利面板 GP 行呈现(C5:GP 行只上冒险终局面板; # 断言面 = summary 键,无影子账清零转存)---- # (置于战斗态断言之后——合成收束会灰化站桩并清 settlement 引用,具污染性) + # oracle: design/gdd/ux/settlement-summary.md:20 战利品行=GP 增量+总量(冒险终局面板 GP 行组) var meta_saved = main.meta.save for p2 in ["user://smoke_ui_probe_settle_scratch.json", "user://smoke_ui_probe_settle_scratch.json.tmp"]: if FileAccess.file_exists(p2): From d68c32470e4272f1c56fa4e5b64f39b3222f1e6e Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 05:53:05 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=963/7=C2=B7ui=5Ftune=5Fprobe):=202=E5=A4=84emit?= =?UTF-8?q?=E5=A4=84=E7=BD=AE=E2=80=94=E2=80=94boot=E5=BC=80=E7=AA=97emit?= =?UTF-8?q?=E2=86=92=5Fshow=5Fdebug=5Foverlay=E7=9B=B4=E8=B0=83(=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E6=80=81=E8=A7=92=E6=A0=87=E6=81=92=E9=9A=90main.gd:1?= =?UTF-8?q?38,=E4=BF=A1=E5=8F=B7=E8=B7=AF=E5=BE=84=E5=B7=B2=E9=80=80?= =?UTF-8?q?=E5=BD=B9,:123=E5=9C=A8=E5=86=8C=E5=85=88=E4=BE=8B=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=BF=9D=E7=95=99emit=E5=8F=AA=E4=BC=9A=E5=9C=A8?= =?UTF-8?q?=E9=9A=90=E6=80=81=E5=81=87=E5=BC=80),=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E9=92=AEemit=E2=86=92=E7=9C=9F=E7=82=B9push=5Fclick;=20?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B06?= =?UTF-8?q?=E6=9D=A1(test-conventions:49=E8=B0=83=E8=AF=95=E9=9D=A2=C3=974?= =?UTF-8?q?=E7=BB=84+architecture:168=E7=A7=8D=E5=AD=90=E4=B8=89=E5=88=86+?= =?UTF-8?q?code-conventions:21=E9=9B=B6=E8=BF=9E=E6=8E=A5=E5=B7=A1?= =?UTF-8?q?=E6=A3=80);=20=E8=A7=84=E6=A0=BC=E7=BC=BA=E5=8F=A3=E5=8F=91?= =?UTF-8?q?=E7=8E=B0#1:=20=5Foverlay=5Fzero=5Fmotion=5Fcontract=E9=9B=B6?= =?UTF-8?q?=E5=8A=A8=E6=95=88=E5=A5=91=E7=BA=A6=E6=97=A0=E7=8E=B0=E8=A1=8C?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=8F=A5=E5=8F=AF=E9=94=9A(=E6=BA=90?= =?UTF-8?q?=E5=8D=A117=E5=8D=A1=E2=91=A2=E5=B7=B2=E9=80=80=E5=BD=B9),?= =?UTF-8?q?=E8=AE=B0=E5=8F=91=E7=8E=B0=E4=B8=8D=E9=80=A0=E9=94=9A=E5=BE=85?= =?UTF-8?q?=E6=94=B6=E5=B0=BE=E5=91=88=E6=8A=A5;=20=E6=9C=AC=E6=AD=A5?= =?UTF-8?q?=E5=87=AD=E6=8D=AE:=20ui=5Ftune=5Fprobe=20headless=E7=9B=B4?= =?UTF-8?q?=E8=B7=91PASS=20exit=3D0(N=3D10=E6=A8=A1=E6=8B=9F=E5=85=A8?= =?UTF-8?q?=E9=93=BE=E8=B7=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/ui_tune_probe.gd | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/game/probe/ui_tune_probe.gd b/game/probe/ui_tune_probe.gd index b90c2aca..3c910611 100644 --- a/game/probe/ui_tune_probe.gd +++ b/game/probe/ui_tune_probe.gd @@ -43,7 +43,8 @@ func _ready() -> void: # ① 全局浮层角标开窗(调试入口=UI 最顶层浮层,任何 UI 不遮挡—— # main 直接子层、LAYER_CONTRACT 末位 float 类;boot 菜单优先不开浮窗,角标真实点击路径开窗; # 悬浮于菜单之上——开始层不隐藏;树序新基准=登记册末位(确认弹窗之后=绘制序最上)) - main.ui.screens.debug_badge.pressed.emit() + # oracle: design/engineering/test-conventions.md:49 调试面=DebugOverlay/DS_DEBUG——发布态恒隐无 UI 开启口、开发期临时启用(①开窗层级组) + main._show_debug_overlay() # 发布态角标恒隐(main.gd:138 _debug_entry 缺省 false)——角标信号路径已退役(:123 同款先例),开窗经组合根直调承载 await get_tree().process_frame if not main.ui.modals.menu.debug_overlay.visible: problems.append("调试浮层角标点击后浮窗未开(全局浮层入口失守)") @@ -56,12 +57,14 @@ func _ready() -> void: if str(boosted_atk) not in main.ui.modals.menu.debug_overlay.effect_text() or str(boosted_hp) not in main.ui.modals.menu.debug_overlay.effect_text(): problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text()) # ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照 + # oracle: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组) main.ui.modals.menu.debug_overlay.press_apply_for_probe() # ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线) main._show_debug_overlay() main.ui.modals.menu.debug_overlay.press_reset_for_probe() # ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位; # 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕) + # oracle: design/engineering/test-conventions.md:49 调试面批量模拟链路:N=10 输出关卡完成率/剩余 HP/败北节点(⑤组) main.ui.modals.menu.debug_overlay.set_sliders(100, 0) main.ui.modals.menu.debug_overlay.set_sim_count(10) main.ui.modals.menu.debug_overlay.press_simulate_for_probe() @@ -116,6 +119,7 @@ func _ready() -> void: # ⑨ 对局中可开(18卡⑦ P2 甲/T2 防护——S1+ 停机点回归闸):战斗屏角标真实信号路径开窗; # 棋盘输入不锁、状态角标不置灰(「默认开×遮挡复活」下必锁死的破约回归面); # 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复 + # oracle: design/engineering/test-conventions.md:49 调试面对局态:不锁棋盘输入/两钮禁用/当局数据只读一致(⑨组) var dbg_badge: Button = main.ui.screens.debug_badge if dbg_badge == null or dbg_badge.visible: problems.append("对局中调试浮层角标非恒隐(发布态语义失守——卡⑦ 删写死行批)") @@ -150,7 +154,7 @@ func _ready() -> void: if close_btn == null: problems.append("浮窗关闭钮缺失(真实收窗路径中断)") else: - close_btn.pressed.emit() + ProbeUiUtil.push_click(main, close_btn) if main.ui.modals.menu.debug_overlay.visible or main.ui.flow.locks_input(): problems.append("对局中关闭浮窗后态失守(visible=%s locked=%s)" % [str(main.ui.modals.menu.debug_overlay.visible), str(main.ui.flow.locks_input())]) @@ -159,6 +163,7 @@ func _ready() -> void: _overlay_zero_motion_contract(main, problems) # ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面): # 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42 + # oracle: design/engineering/architecture/architecture.md:168 seed_value=组合根公开服务面(⑩种子三分判定单点组) main.ui.modals.menu.debug_overlay.seed_edit.text = "777" if main.seed_value() != 777: problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)") @@ -199,6 +204,7 @@ func _ready() -> void: # 全连接巡检——白名单={视图自身八方法 + 注册面模态编排口 ui.flow.close(closed 外发消费—— # 注册面批把该连接目标从组合根转发壳改为 flow 真方法,机检白名单随批登记同一连接)}; # 当局数据区块=纯标签零连接在此被一并约束(新增任何连接=红须入册审查)。 +# oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(白名单外零连接巡检组) func _overlay_readonly_contract(main: Node, problems: Array) -> void: var overlay: Control = main.ui.modals.menu.debug_overlay var stack: Array = [overlay] From e0b22f40fb068659f4f1d166abd2f2d3b4de4f15 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 05:55:36 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=964/7=C2=B7shot=5Fprobe):=207=E5=A4=84emit?= =?UTF-8?q?=E2=86=92=E7=9C=9F=E8=BE=93=E5=85=A5=E2=80=94=E2=80=94=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=BC=80/=E5=85=B3=E3=80=81=E7=94=9F=E6=B6=AF?= =?UTF-8?q?=E5=85=B3=E3=80=81=E7=8A=B6=E6=80=81=E9=9D=A2=E6=9D=BF=E5=BC=80?= =?UTF-8?q?/=E5=85=B3=3D=E7=9C=9F=E7=82=B9find=5Fbutton=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E9=92=AE,=E7=AF=9D=E7=81=AB=3Dpush=5Fclick=5Fat=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=B8=AD=E5=BF=83,=E4=BA=8C=E6=AC=A1=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=3D=E7=9C=9F=E7=82=B9=5Fconfirm=5Fbtn(ui=5Fclick=5Fpro?= =?UTF-8?q?be:702=E5=85=88=E4=BE=8B);=20=E5=88=A4=E6=8D=AE=E5=87=BA?= =?UTF-8?q?=E5=A4=84=E6=B3=A8=E8=AE=B09=E6=9D=A1(code-conventions:54?= =?UTF-8?q?=E9=81=AE=E6=8C=A1=E7=BA=AA=E5=BE=8B+=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=8E=E6=9C=AC=E5=9C=B0=E5=8C=96:95+camp.md:45/16/22+level-?= =?UTF-8?q?map:11/77+battle.md:122+HUD=E6=A1=86=E6=9E=B6:264/277+test-conv?= =?UTF-8?q?entions:84=E6=88=AA=E5=9B=BE=E9=87=87=E9=9B=86);=20=E6=9C=AC?= =?UTF-8?q?=E6=AD=A5=E5=87=AD=E6=8D=AE:=20shot=5Fprobe=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=8C=96=E7=9B=B4=E8=B7=91PASS=20exit=3D0(=E4=B8=83=E5=B1=8F12?= =?UTF-8?q?80x800=E5=85=A8=E5=AD=98,=E9=97=AE=E9=A2=98=3D0,battle=20draw?= =?UTF-8?q?=5Fcalls=3D194<200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/shot_probe.gd | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/game/probe/shot_probe.gd b/game/probe/shot_probe.gd index 1981c89e..eb66e9bd 100644 --- a/game/probe/shot_probe.gd +++ b/game/probe/shot_probe.gd @@ -46,6 +46,7 @@ func _ready() -> void: await get_tree().process_frame # ① 菜单(启动态)——无帮助文腿(玩法速览牌不在画面,checker 无对应断言段); # 无副标题件,菜单文字锚只剩标题 + # oracle: design/engineering/code-conventions.md:54 模态覆盖层层级规则——boot 态顶层=start_overlay、菜单文字在屏(菜单组) # 自检一:boot 态顶层可见全屏层=start_overlay(内容层树遍历——屏壳是容器不当候选: # camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳) var top: Node = null @@ -82,11 +83,12 @@ func _ready() -> void: _meta[String(pair[0])] = ProbeUiUtil.label_rect(main.ui.screens.start_overlay, String(pair[1])) await _snap("menu") # ①b 设置面(settingsbtn 真实信号路径,开合同款真实钮) + # oracle: design/gdd/systems/表现与支撑/设置与本地化.md:95 最小设置面已兑现:音量/减动效/全屏/退出,载体=独立设置存储+设置界面视图(设置组) var setbtn: Button = ProbeUiUtil.find_button(main.ui.screens.start_overlay, "设置", "prefix") if setbtn == null: _problems.append("设置入口缺失(开始层 settingsbtn)") else: - setbtn.pressed.emit() + ProbeUiUtil.push_click(main, setbtn) for i in 3: await get_tree().process_frame if not main.ui.modals.menu.settings_overlay.visible: @@ -98,9 +100,10 @@ func _ready() -> void: if set_close == null: _problems.append("设置面返回钮缺失") else: - set_close.pressed.emit() + ProbeUiUtil.push_click(main, set_close) await get_tree().process_frame # ①c 生涯页(函数直调中间态——卡② mn10:careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径) + # oracle: design/gdd/ux/camp.md:45 生涯页与四子界面同壳同形态(生涯组) if true: main._open_career() for i in 3: @@ -114,27 +117,31 @@ func _ready() -> void: if career_close == null: _problems.append("生涯页返回钮缺失") else: - career_close.pressed.emit() + ProbeUiUtil.push_click(main, career_close) for i in 3: await get_tree().process_frame if main.ui.modals.menu.career_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("生涯页关闭后页未隐藏或开始层未恢复") # ② 营地场景图(进档开层新路径——卡② mn9;截图=可点击场景图形态——卡④ 契约 9 V1 验收载体) + # oracle: design/gdd/ux/camp.md:16 营地=可点击场景图(七物件命中区表——场景图形态组) if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_shot_probe_camp"): _problems.append("营地入口缺失(选档层新档钮——新路径驱动中断)〔句柄子容器批 V1〕") else: await get_tree().process_frame await _snap("camp") - main.ui.modals.camp.scene.object_pressed.emit(&"campfire") # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"campfire").get_center()) # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5) await get_tree().process_frame - main.ui.modals.menu.confirm_dialog.confirmed.emit() # 二次确认离场(关层收闭——选档层随收) + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 二次确认离场(关层收闭——选档层随收) + # oracle: design/gdd/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(篝心动线组) await get_tree().process_frame # ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10) + # oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(关卡画面出图组) main._start_adventure_from_menu() for i in 4: await get_tree().process_frame await _snap("level") # GP 常驻徽章断言(读数=runner.gp 现值,刷新=写路径回执后重查〔open_choices〕) + # oracle: design/gdd/ux/level-map.md:77 GP 常驻徽章=GP 图标+数字(冒险钱包读数;写路径回执后重查) var gp_label: Label = ProbeUiUtil.find_label(main.ui.modals.run.level_screen, "◈ ") if gp_label == null or not gp_label.is_visible_in_tree(): _problems.append("GP 徽章不可见(关卡画面顶栏)") @@ -143,6 +150,7 @@ func _ready() -> void: if gp_label.text != expect: _problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect]) # ④ 战斗(节点一——真实选路提交路径 press_candidate,ui_click_probe :199 同款) + # oracle: design/gdd/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组) if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible: main.ui.modals.run.level_screen.press_candidate(0) for i in 10: @@ -154,17 +162,19 @@ func _ready() -> void: _meta["stage_view"] = _node_rect(main.ui.battle.stage) _meta["board_zone"] = _node_rect(main.ui.battle.board_zone) # draw calls 真机测量(基线 270→目标 <200;窗口化渲染帧读数, + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 同屏 draw calls < 200(战斗屏性能预算组) # 取值时机=战斗截图同帧后一帧——呈现终态满负荷) await get_tree().process_frame var dc := int(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)) _meta["battle_draw_calls"] = dc print("[shot_probe] battle draw_calls=%d(基线 270/目标 <200)" % dc) # ⑤ 状态面板(战斗屏状态角标真实信号路径;层在战斗屏之上开合) + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 模态打开遮挡或输入锁定、关闭逐层恢复(状态面板组) var pb: Button = main.ui.battle.board_zone.status_badge if pb == null or not pb.visible or pb.disabled: _problems.append("状态面板入口角标缺失/置灰(战斗屏 board_zone)") else: - pb.pressed.emit() + ProbeUiUtil.push_click(main, pb) for i in 3: await get_tree().process_frame if not main.ui.modals.run.status_panel.visible: @@ -176,7 +186,7 @@ func _ready() -> void: if status_close == null: _problems.append("状态面板继续钮缺失") else: - status_close.pressed.emit() + ProbeUiUtil.push_click(main, status_close) await get_tree().process_frame if main.ui.modals.run.status_panel.visible: _problems.append("状态面板关闭后未隐藏(继续钮路径)") @@ -201,6 +211,7 @@ func _ready() -> void: func _snap(key: String) -> void: + # oracle: design/engineering/test-conventions.md:84 golden 测试项扩项——截图采集清单扩项、实跑截图冻结(截图采集组) await RenderingServer.frame_post_draw await RenderingServer.frame_post_draw var img: Image = get_viewport().get_texture().get_image() From 4f48c40fd56ac2026178194e5afcb7e5273306c0 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 05:59:53 +0800 Subject: [PATCH 09/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=965/7=C2=B7screen=5Fflow=5Fprobe):=2014?= =?UTF-8?q?=E5=A4=84emit=E2=86=9213=E5=A4=84=E7=9C=9F=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E2=80=94=E2=80=94=E7=AF=9D=E7=81=AB/=E7=AA=97=E5=A3=B3?= =?UTF-8?q?=E8=BD=AE=E8=BD=AC/V6/V7/V8=E5=9C=BA=E6=99=AF=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E5=8C=BApush=5Fclick=5Fat,=E4=BA=8C=E6=AC=A1=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E7=9C=9F=E7=82=B9=5Fconfirm=5Fbtn,=E9=80=89=E6=A1=A3=E8=BF=94?= =?UTF-8?q?=E5=9B=9E/=E7=BB=88=E5=B1=80=E7=BB=A7=E7=BB=AD/=E7=94=9F?= =?UTF-8?q?=E6=B6=AF=E8=BF=94=E5=9B=9E/=E6=B8=85=E7=AA=97=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E9=92=AE=E7=9C=9F=E7=82=B9,esc=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E4=B8=BB=E8=8F=9C=E5=8D=95=E7=9C=9F=E7=82=B9=5Freturn=5Fbtn;?= =?UTF-8?q?=20V5b=E4=B8=80=E5=A4=84=E4=BF=9D=E7=95=99emit=E5=B9=B6?= =?UTF-8?q?=E6=B3=A8=E8=AE=B0(V5b=E8=A2=AB=E6=B5=8B=E9=9D=A2=3D=E7=AA=97?= =?UTF-8?q?=E5=BC=80=E6=80=81=E9=81=AE=E7=BD=A9=E6=8C=A1=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E7=82=B9=E9=80=89,=E5=89=8D=E6=8F=90=E5=8F=AA=E8=83=BD?= =?UTF-8?q?=E4=BF=A1=E5=8F=B7=E6=B3=A8=E5=85=A5);=20=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B013=E6=9D=A1=E9=80=90?= =?UTF-8?q?=E6=AE=B5=E6=8C=82(architecture:168=E4=BA=92=E6=96=A5=E5=88=A4?= =?UTF-8?q?=E6=8D=AE=C3=972+test-conventions:49=20boot=E7=BB=84+camp.md:10?= =?UTF-8?q?/22/45=C3=973+=E5=AD=98=E6=A1=A3=E5=BA=8F=E5=88=97=E5=8C=96:61?= =?UTF-8?q?=E9=80=89=E6=A1=A3+level-map:11/42+battle.md:57+settlement-summ?= =?UTF-8?q?ary:12);=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20screen=5Fflo?= =?UTF-8?q?w=5Fprobe=20headless=E7=9B=B4=E8=B7=91PASS=20exit=3D0(=E5=85=AD?= =?UTF-8?q?=E6=AD=A5=E4=B8=BB=E6=B5=81=E7=A8=8B+=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E5=85=A8=E7=BB=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/screen_flow_probe.gd | 41 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/game/probe/screen_flow_probe.gd b/game/probe/screen_flow_probe.gd index d02f7097..17a26a6a 100644 --- a/game/probe/screen_flow_probe.gd +++ b/game/probe/screen_flow_probe.gd @@ -69,12 +69,14 @@ func _seals_text(f: Dictionary) -> String: # ---- 流程状态位(与可见性是两回事,逐步单独判)---- func _expect_adventure_seal(step: String, expect: bool) -> void: + # oracle: design/engineering/architecture/architecture.md:168 三屏壳与呈现装配——屏级互斥与流程状态位判据宿主面(状态位判据组) var got: bool = main.run_scope != null and main.run_scope.is_adventure if got != expect: _problems.append("%s:流程状态位 is_adventure 应为 %s,实测 %s" % [step, str(expect), str(got)]) func _expect_exclusive(step: String, expect: String) -> void: + # oracle: design/engineering/architecture/architecture.md:168 屏壳切换与呈现装配(屏级互斥组) var f: Dictionary = _screen_seals() var pairs := [["菜单", "营地"], ["菜单", "冒险"], ["营地", "冒险"], ["终局", "营地"], ["终局", "菜单"]] for pair in pairs: @@ -101,6 +103,7 @@ func _frames(n: int = 2) -> void: # ---- ① boot:菜单独占 ---- func _step_boot_menu() -> void: + # oracle: design/engineering/test-conventions.md:49 调试面发布态恒隐、无 UI 开启口(boot 组) _expect_exclusive("①boot", "菜单") _expect_adventure_seal("①boot", false) if main.ui.modals.menu.debug_overlay != null and main.ui.modals.menu.debug_overlay.visible: @@ -109,6 +112,7 @@ func _step_boot_menu() -> void: # ---- ② 菜单 → 营地(真实按钮信号路径)---- func _step_menu_to_camp() -> void: + # oracle: design/gdd/ux/camp.md:10 营地=存档内局外枢纽,存档语境可达(菜单→营地组) # 营地经进档开层(卡② mn9 新路径——campbtn 已撤;槽模板注入隔离命名空间) if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_camp"): _problems.append("②菜单→营地:选档层新档钮缺失(新路径驱动中断)") @@ -119,14 +123,15 @@ func _step_menu_to_camp() -> void: # ---- ③ 营地 → 菜单(回退路径:篝火二次确认动线——卡④ 营地唯一回菜单出口;选档层随关层收闭同收)---- func _step_camp_back_to_menu() -> void: + # oracle: design/gdd/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(营地→菜单组) if main.ui.modals.camp.scene == null: _problems.append("③营地→菜单:营地场景根句柄缺失〔句柄子容器批 V1〕") return - main.ui.modals.camp.scene.object_pressed.emit(&"campfire") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"campfire").get_center()) await _frames() if not main.ui.modals.menu.confirm_dialog.visible: _problems.append("③篝火:二次确认弹窗未现(camp.md v2.2 必须先确认)〔营地图批 V7〕") - main.ui.modals.menu.confirm_dialog.confirmed.emit() # 二次确认离场(confirmed 先于关层——卡④ T4) + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 二次确认离场(confirmed 先于关层——卡④ T4) await _frames() _expect_exclusive("③营地→菜单(回退)", "菜单") @@ -135,6 +140,7 @@ func _step_camp_back_to_menu() -> void: # ---- 选档界面开合(卡③ sl10):函数直调开层(「进入存档」钮归卡② mn3)→屏级互斥采样→ # 返回钮真实信号路径关闭→ESC 语境不响应(卡⑤ 契约 2 范围外——零实现即断言)。---- func _step_save_select_open_back() -> void: + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 激活态由选档界面的「新档」或「进档」建立(选档开合组) ProbeUiUtil.isolate_save(main, "smoke_screen_flow_savesel", false, false, true) main.save_manager.slots.attach_slot_template("user://smoke_screen_flow_savesel_slot_%d.json") var esb: Button = ProbeUiUtil.find_button(main.ui.screens.start_overlay, "进入存档", "prefix") @@ -150,7 +156,7 @@ func _step_save_select_open_back() -> void: if back_btn == null: _problems.append("[savesel] sl10 返回钮缺失(页壳顶条)") else: - back_btn.pressed.emit() + ProbeUiUtil.push_click(main, back_btn) await get_tree().process_frame if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[savesel] sl10 返回后选档未隐藏或开始层未恢复(关闭链失守)") @@ -181,6 +187,7 @@ func seals_check_single(f: Dictionary) -> bool: func _step_menu_to_adventure() -> void: + # oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(菜单→冒险组) # 开始冒险钮已随三入口化撤出——函数直调中间态(卡② mn10;卡④ 大门两分支换代) main._start_adventure_from_menu() await _frames(3) @@ -194,6 +201,7 @@ func _step_menu_to_adventure() -> void: # ---- ⑤ 冒险内屏切换:关卡地图屏 ⇄ 战斗屏(屏内两态,屏级仍独占)---- func _step_level_to_battle_and_back() -> void: + # oracle: design/gdd/ux/battle.md:57 Entry & Exit Points——进出战斗屏(冒险内切换组) if not main.ui.modals.run.level_screen.visible: _problems.append("⑤冒险内切换:前置不在关卡地图屏") return @@ -213,6 +221,7 @@ func _step_level_to_battle_and_back() -> void: # ---- ⑥ 放弃 → 终局(放弃路径=回退路径的非正常出口)---- func _step_abandon_to_end() -> void: + # oracle: design/gdd/ux/level-map.md:42 放弃冒险(任意选路停点)→ 冒险落幕(放弃→终局组) if main.run_scope == null or main.run_scope.adventure == null: _problems.append("⑥放弃→终局:前置无冒险实例") return @@ -225,10 +234,11 @@ func _step_abandon_to_end() -> void: # ---- ⑦ 终局 → 菜单(终局出口返回开始界面)---- func _step_end_back_to_menu() -> void: + # oracle: design/gdd/ux/settlement-summary.md:12 冒险总结结算=冒险落幕时终局覆盖层(终局→菜单组) if main.ui.modals.run.end_overlay == null or not main.ui.modals.run.end_overlay.visible: _problems.append("⑦终局→菜单:前置不在终局") return - main.ui.modals.run.end_overlay.continue_pressed.emit() + ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) # 真点继续钮(_btn 唯一钮——ui_click_probe:543 同源) await _frames(3) _expect_exclusive("⑦终局→菜单", "菜单") if main.run_scope != null and main.run_scope.is_adventure: @@ -237,6 +247,7 @@ func _step_end_back_to_menu() -> void: # ---- ③' 营地 Esc 浮窗+HUD(卡⑤——真实键盘合成路径 push_input;四态路由+三出口+语境分支+余额单口)---- func _step_camp_esc_and_hud() -> void: + # oracle: design/gdd/ux/camp.md:45 ESC 优先序:有功能窗口先关最上层窗,无窗才进 ESC 浮窗(Esc 段组) ProbeUiUtil.isolate_save(main, "smoke_screen_flow_esc", false, false, true) if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_esc"): _problems.append("[esc] 营地入口缺失(选档层新档钮驱动中断)〔ESC批 V1〕") @@ -254,7 +265,7 @@ func _step_camp_esc_and_hud() -> void: await _frames() if main.ui.modals.camp.esc_overlay.visible: _problems.append("[esc] echo 事件唤起浮窗(触发沿 pressed 且非 echo 失守)〔ESC批 V1〕") - main.ui.modals.camp.scene.object_pressed.emit(&"dummy") # 子界面开态 + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) # 子界面开态 await _frames() _push_esc() await _frames() @@ -285,7 +296,7 @@ func _step_camp_esc_and_hud() -> void: if not main.ui.modals.camp.esc_overlay.visible or main.ui.screens.start_overlay.visible: _problems.append("[esc] 关设置后未回浮窗或回弹主菜单(契约 3 语境分支失守)〔ESC批 V4〕〔ESC批 B3〕") # 返回主菜单动线(契约 4——浮窗自收+营地隐+选档收+菜单显) - main.ui.modals.camp.esc_overlay.return_requested.emit() + ProbeUiUtil.push_click(main, main.ui.modals.camp.esc_overlay._return_btn) # 真点返回主菜单钮(camp_esc_overlay 底栏钮) await _frames() if main.ui.modals.camp.esc_overlay.visible or main.ui.modals.camp.scene.visible or main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[esc] 返回主菜单动线失守(浮窗自收/营地隐/选档收/菜单显)〔ESC批 V3〕〔ESC批 B3〕") @@ -300,6 +311,7 @@ func _step_camp_esc_and_hud() -> void: # 判的是呈现形态换壳(近全屏面板→居中定尺寸窗壳 720×640):窗几何/场景根留底/遮罩 0.6/关闭钮/ # 互斥单开/ESC 优先序/窗内滚动/生涯同壳——行为面(开合路由)零改,只钉窗壳可观测提交面。 func _step_camp_window_shell() -> void: + # oracle: design/gdd/ux/camp.md:45 五功能窗统一窗壳、五窗互斥单开、各窗自带关闭钮(窗壳 V1–V8 组) ProbeUiUtil.isolate_save(main, "smoke_screen_flow_winshell", false, false, true) if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_winshell"): _problems.append("[winshell] 营地入口缺失(选档层新档钮驱动中断)〔窗口化批 V1〕") @@ -323,11 +335,11 @@ func _step_camp_window_shell() -> void: if prev_back == null: _problems.append("[winshell] %s 底栏「返回营地」钮缺失〔窗口化批 V4〕" % String(opened.back())) else: - prev_back.pressed.emit() + ProbeUiUtil.push_click(main, prev_back) await _frames() if prev.visible or not main.ui.modals.camp.scene.visible: _problems.append("[winshell] %s 关闭钮按下未隐窗或场景根失守〔窗口化批 V4〕" % String(opened.back())) - main.ui.modals.camp.scene.object_pressed.emit(k) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(k).get_center()) await _frames() opened.append(k) var view: Control = views[k] @@ -362,7 +374,7 @@ func _step_camp_window_shell() -> void: if vis_n != 1: _problems.append("[winshell] %s 开态五窗 visible 计数=%d 应=1(互斥单开失守)〔窗口化批 V5〕" % [String(k), vis_n]) # 树窗点选遮挡(V5b——树窗开态向锻炉命中区推真实鼠标点:被遮罩挡=零新窗零响应) - main.ui.modals.camp.scene.object_pressed.emit(&"dummy") + main.ui.modals.camp.scene.object_pressed.emit(&"dummy") # 前提注入(保留 emit):V5b 被测面=窗开态遮罩挡场景点选——真点击会被遮罩吞掉,树窗开态只能信号注入建立 await _frames() var tree_view: Control = views[&"dummy"] if not tree_view.visible: @@ -383,7 +395,7 @@ func _step_camp_window_shell() -> void: # 随栈 close 恢复语义逐层清账,V6/V8 从干净单窗态起步) await _close_all_windows(views) # V6 ESC 先关窗(生涯窗开态 Esc→关生涯窗、浮窗不唤起) - main.ui.modals.camp.scene.object_pressed.emit(&"notebook") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"notebook").get_center()) await _frames() if not career.visible: _problems.append("[winshell] 生涯窗未开(V6 前置中断)〔窗口化批 V6〕") @@ -395,7 +407,7 @@ func _step_camp_window_shell() -> void: if main.ui.modals.camp.esc_overlay.visible: _problems.append("[winshell] 关窗同拍唤起浮窗(应无窗才进浮窗)〔窗口化批 V6〕") # V7 内容超高窗内滚动(树窗+真实投影喂加长行册) - main.ui.modals.camp.scene.object_pressed.emit(&"dummy") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) await _frames() if not tree_view.visible: _problems.append("[winshell] 树窗未开(V7 前置中断)〔窗口化批 V7〕") @@ -420,7 +432,7 @@ func _step_camp_window_shell() -> void: # 清窗经真实钮(同 V6 前置——干净单窗态起步) await _close_all_windows(views) # V8 生涯同壳(窗板同几何+返回钮在位+内容列滚动面在) - main.ui.modals.camp.scene.object_pressed.emit(&"notebook") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"notebook").get_center()) await _frames() if not career.visible: _problems.append("[winshell] 生涯窗未开(V8 前置中断)〔窗口化批 V8〕") @@ -445,7 +457,7 @@ func _step_camp_window_shell() -> void: # 段尾收闭:生涯窗经真实「返回」钮关闭(on_camp_closed 关闭名单不含生涯窗——防残留泄段) var career_back: Button = ProbeUiUtil.find_button(career, "返回", "prefix") if career_back != null: - career_back.pressed.emit() + ProbeUiUtil.push_click(main, career_back) await _frames() if career.visible: _problems.append("[winshell] 生涯「返回」钮未隐窗〔窗口化批 V4〕") @@ -455,6 +467,7 @@ func _step_camp_window_shell() -> void: # 清窗经真实钮(逐窗巡检 visible→钮 pressed——栈 close 恢复账随层清空) func _close_all_windows(views: Dictionary) -> void: + # oracle: design/gdd/ux/camp.md:45 各窗自带关闭钮(清窗组) for k: StringName in [&"forge", &"chest", &"dummy", &"board"]: var w: Control = views[k] if not w.visible: @@ -463,7 +476,7 @@ func _close_all_windows(views: Dictionary) -> void: if back == null: _problems.append("[winshell] %s 底栏「返回营地」钮缺失〔窗口化批 V4〕" % String(k)) continue - back.pressed.emit() + ProbeUiUtil.push_click(main, back) await _frames() From 5e6cec8353b0535af3a46bfd4c0114e88fc25ca8 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 06:31:32 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=966/7=C2=B7ui=5Fstruct=5Fprobe):=204=E5=A4=84em?= =?UTF-8?q?it=E2=86=92=E7=9C=9F=E8=BE=93=E5=85=A5(=E9=94=BB=E7=82=89/?= =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=3D=E5=9C=BA=E6=99=AF=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E5=8C=BA=E7=9C=9F=E7=82=B9,forge=E5=85=B3=E7=AA=97=3D?= =?UTF-8?q?=E7=9C=9F=E7=82=B9=E8=BF=94=E5=9B=9E=E8=90=A5=E5=9C=B0=E9=92=AE?= =?UTF-8?q?,gate=3D=E6=8C=89=E4=B8=8B=E5=9C=A8=E5=91=BD=E4=B8=AD=E5=8C=BA+?= =?UTF-8?q?=E6=8A=AC=E6=9D=86=E7=A6=BB=E5=9C=BA=E9=98=B2=E8=AF=AF=E8=A7=A6?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E2=80=94=E2=80=94=E5=9C=BA=E6=99=AF=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E6=B2=BF=3D=E6=8A=AC=E6=9D=86,=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E9=9A=8F=E6=8A=AC=E6=9D=86=E5=90=8C=E5=B8=A7=E5=BC=80,?= =?UTF-8?q?=E6=8A=AC=E6=9D=86=E8=90=BD=E5=8E=9F=E4=BD=8D=E7=BB=8F=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E4=BC=A0=E6=92=AD=E8=AF=AF=E8=A7=A6=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E9=92=AE=E5=AE=9E=E8=AF=81=E5=86=92=E9=99=A9=E8=AF=AF=E5=90=AF?= =?UTF-8?q?,=E6=8A=AC=E6=9D=86=E8=90=BD(2,2)=E4=B8=AD=E6=80=A7=E7=82=B9);?= =?UTF-8?q?=202=E5=A4=84=E4=BF=9D=E7=95=99emit+=E5=89=8D=E6=8F=90=E6=B3=A8?= =?UTF-8?q?=E8=AE=B0(route=E9=80=89=E8=B7=AF/=E5=9B=BE=E4=BE=8B=E5=BC=80?= =?UTF-8?q?=E5=90=AF=E2=80=94=E2=80=94emit=E9=80=89=E8=B7=AF=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E4=B8=8Bbattle=5Froot=E6=9C=AA=E5=8F=8A=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=B8=94=E9=94=81=E5=9C=A8,=E7=9C=9F=E7=82=B9?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E8=BE=BE);=20=E5=88=A4=E6=8D=AE=E5=87=BA?= =?UTF-8?q?=E5=A4=84=E6=B3=A8=E8=AE=B09=E6=9D=A1(camp.md:10/19/45+level-ma?= =?UTF-8?q?p:3+battle.md:154+HUD=E6=A1=86=E6=9E=B6:88+code-conventions:54/?= =?UTF-8?q?21=C3=972+=E5=AD=98=E6=A1=A3=E5=BA=8F=E5=88=97=E5=8C=96:61+arch?= =?UTF-8?q?itecture:168);=20=E6=9C=AC=E6=89=B9=E5=8F=91=E7=8E=B0=E8=AE=B0?= =?UTF-8?q?=E8=B4=A6:=20=E2=91=A0route=E4=BF=A1=E5=8F=B7=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=B1=8F=E5=B9=95=E5=88=87=E6=8D=A2=E6=97=B6=E5=BA=8F=E2=80=94?= =?UTF-8?q?=E2=80=94emit=E5=90=8Ebattle=5Froot=E6=8C=81=E7=BB=AD=E4=B8=8D?= =?UTF-8?q?=E6=98=BE=E7=A4=BA,battle=5Fhud=E5=AE=A1=E8=AE=A1=E9=95=BF?= =?UTF-8?q?=E6=9C=9F=E7=A9=BA=E8=BD=AC=E9=87=87=E6=A0=B7(=E5=81=87?= =?UTF-8?q?=E7=BB=BF=E9=9D=A2)=20=E2=91=A1=E7=9C=9F=E7=82=B9=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E9=87=87=E6=A0=B7=E5=8F=98=E5=AE=9E=E6=9A=B4=E9=9C=B2?= =?UTF-8?q?=E5=81=9C=E6=89=8B(64)/=E5=9B=BE=E4=BE=8B(56)=E5=90=8C=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E5=AE=BD=E5=BA=A6=E4=B8=8D=E7=AD=89(=E5=9C=A8?= =?UTF-8?q?=E5=86=8Cequal=5Fwidth=E7=BA=AA=E5=BE=8B=E5=BF=85=E7=BA=A2)=20?= =?UTF-8?q?=E2=91=A2=E5=9B=BE=E4=BE=8B=E5=BC=80=E5=90=AF=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=89=BF=E8=BD=BD=E5=BE=85=E2=91=A0=E8=A3=81=E5=86=B3?= =?UTF-8?q?=E5=90=8E=E8=A1=A5=E2=80=94=E2=80=94=E4=B8=89=E9=A1=B9=E5=BE=85?= =?UTF-8?q?=E8=A3=81=E6=94=B6=E5=B0=BE=E5=91=88=E6=8A=A5;=20=E6=9C=AC?= =?UTF-8?q?=E6=AD=A5=E5=87=AD=E6=8D=AE:=20ui=5Fstruct=5Fprobe=20headless?= =?UTF-8?q?=E7=9B=B4=E8=B7=91PASS=20exit=3D0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/ui_struct_probe.gd | 39 ++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/game/probe/ui_struct_probe.gd b/game/probe/ui_struct_probe.gd index e69662fb..d15fe3f5 100644 --- a/game/probe/ui_struct_probe.gd +++ b/game/probe/ui_struct_probe.gd @@ -68,7 +68,7 @@ func _ready() -> void: problems.append("[camp] 场景子节点序≠layout 表 z 序(逐层相等失守)〔营地图批 V1〕") if not rect_ok: problems.append("[camp] 层贴图矩形≠layout 表(逐值一致失守)〔营地图批 V1〕") - main.ui.modals.camp.scene.object_pressed.emit(&"forge") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"forge").get_center()) _settle() if not main.ui.modals.camp.forge.visible: problems.append("[camp] 点选锻炉→锻造子界面未开(场景点选路由中断)〔营地图批 V2〕") @@ -81,7 +81,7 @@ func _ready() -> void: scene_buttons += 1 if scene_buttons != 0: problems.append("[camp] 场景根态存在文字按钮(反参照纪律——禁文字按钮列表形态)〔营地图批 V9〕") - main.ui.modals.camp.forge.closed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.forge, "返回营地")) _settle() main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3) _settle() @@ -89,10 +89,11 @@ func _ready() -> void: # ---- 生涯页:入口=帐篷旁笔记本(卡④ T3 改挂——场景点选信号真实路由→组合根 _open_career)→ # 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。 #(登记序/树序契约统一在全模态 _modal_registry_contract——boot 段跑)---- + # oracle: design/gdd/ux/camp.md:19 帐篷旁笔记本→生涯记录;生涯页返回钮口径由结构校验兜底(生涯段) # 营地语境:开始层全程隐藏(笔记本入口在营地场景根——关生涯回营地不回开始层,卡④ T3 语境分支) main._camp_pres.open_camp() # 重开营地场景根(笔记本入口语境) _settle() - main.ui.modals.camp.scene.object_pressed.emit(&"notebook") + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"notebook").get_center()) _settle() if not main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[career] 生涯页未打开或开始层未隐藏(笔记本改挂路径断裂)〔营地图批 V5〕") @@ -105,11 +106,26 @@ func _ready() -> void: if main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[career] 关闭后页未隐藏或开始层回弹(营地语境分支失守)〔营地图批 V5〕") # ---- Esc 浮窗结构段(卡⑤——三钮在位且仅三+零动效契约+登记序在册+debug_badge 不波及)---- + # oracle: design/gdd/ux/camp.md:45 ESC 优先序:有窗先关最上层窗、无窗才进浮窗;各窗自带关闭钮(Esc 段) main._camp_pres.open_camp() _settle() - main.ui.modals.camp.scene.object_pressed.emit(&"gate") + var gpos_gate: Vector2 = main.ui.modals.camp.scene.get_hotspot_rect(&"gate").get_center() + var gdown := InputEventMouseButton.new() + gdown.button_index = MOUSE_BUTTON_LEFT + gdown.pressed = true + gdown.position = gpos_gate + gdown.global_position = gpos_gate + main.get_viewport().push_input(gdown, true) # 按下在 gate 命中区 + await get_tree().process_frame + await get_tree().process_frame + var gup := InputEventMouseButton.new() + gup.button_index = MOUSE_BUTTON_LEFT + gup.pressed = false + gup.position = Vector2(2, 2) # 抬杆落点避开弹窗钮域——场景命中沿=抬杆,弹窗随抬杆同帧开;抬杆落原位会经事件传播误触确认钮(实证:冒险误启)。抬杆离场=不经历弹窗开关,段断言(Esc 路由)在净态成立 + gup.global_position = Vector2(2, 2) + main.get_viewport().push_input(gup, true) _settle() - main.ui.modals.menu.confirm_dialog.cancelled.emit() # 关大门弹窗(清场——浮窗段前提) + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._cancel_btn) # 关大门弹窗(清场——浮窗段前提;无弹窗态=无害空点) _settle() var push_esc := func() -> void: var ev := InputEventKey.new() @@ -197,6 +213,7 @@ func _ready() -> void: _settle() # ---- 冒险簇(真流推进:开冒险→地图选路态→选战斗节点→战斗 HUD)---- + # oracle: design/gdd/ux/level-map.md:3 图上点击选路·候选按钮退役(冒险簇选路真点组) ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_adv", true, false, true) main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,结构采样确定性禁依赖默认分支) main._start_adventure_from_menu() @@ -205,7 +222,7 @@ func _ready() -> void: var battle_id := _pick_battle_node(main) if battle_id >= 0: - main.ui.modals.run.level_screen.route_chosen.emit(battle_id) + main.ui.modals.run.level_screen.route_chosen.emit(battle_id) # 前提注入(保留 emit):本段被测面=战斗 HUD 结构审计,选路机制非其判据面——图上命中真入口由 ui_input_probe case⑫ 承载。附发现呈报:emit 路径下审计采样时 battle_root 尚未显示(审计长期空转采样);真点路径采样变实后暴露停手(64)/图例(56)同容器宽度不等——两项均待裁(收尾呈报) _settle(6) _audit("battle_hud", [main.ui.battle.board_zone, main.ui.battle.info_panel, main.ui.battle.stage]) _standeefit_contract(main) @@ -226,7 +243,8 @@ func _ready() -> void: # ---- 图例层(旧模态、首过结构族):ap_bar legend_toggled # 信号真路径开(board_zone 信号→组合根 _on_legend_toggled→_open_modal)→结构断言族→ # closed 信号真路径关(close()→组合根 _on_modal_closed)。 - main.ui.battle.board_zone.legend_toggled.emit() + # oracle: design/gdd/systems/表现与支撑/HUD框架.md:88 图例面板=模态族:统一登记序栈、入栈出栈、关闭逐层恢复(图例组) + main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() # 前提注入(保留 emit):emit 选路路径下 battle_root 未及显示且输入锁在(实证在案),真点不可达——图例开启的真输入承载随「route 信号路径屏幕切换时序」发现一并呈报待裁 _settle() if not main.ui.modals.run.legend_overlay.visible: problems.append("[legend] 图例层未打开(legend_toggled 路径中断)") @@ -388,6 +406,7 @@ const _PAUSE_READONLY_SIGNALS: Array[String] = [ func _status_readonly_contract(main: Node) -> void: + # oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(status 只读巡检组) var overlay: Control = main.ui.modals.run.status_panel var stack: Array = [overlay] var conn_count := 0 @@ -434,6 +453,7 @@ func _status_zero_motion_contract(main: Node) -> void: # 清单外零连接。白名单={视图自身 _emit_closed〔返回钮〕, 组合根 _on_career_closed};页内任何 # 购买/导航/写入方向的新连接一律红,须先入册审查。 func _career_readonly_contract(main: Node) -> void: + # oracle: design/engineering/code-conventions.md:21 视图零核心写调用、依赖单向(career 只读巡检组) var overlay: Control = main.ui.modals.menu.career_overlay var stack: Array = [overlay] var conn_count := 0 @@ -480,6 +500,7 @@ func _career_zero_motion_contract(main: Node) -> void: # _career_registry_contract 的断言(career=状态面板后/确认弹窗前、树序同步)由本全量合同覆盖) # ⑭选档界面契约(卡③ sl1/sl2/sl3/sl4/sl9——呈现面全部来自喂入数据,视图零自判) func _save_select_contract(main: Node, tpl: String) -> void: + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 三存档位独立文件、激活态由选档界面建立(选档契约组) var sso: Node = main.ui.modals.menu.save_select_overlay # sl1 三槽呈现:槽卡计数==3+零滚动容器(禁滚动长列表形态——契约 1) var rows: VBoxContainer = sso._rows_box @@ -559,6 +580,7 @@ func _collect_labels(node: Node, out: Array[String]) -> void: func _modal_registry_contract(main: Node) -> void: + # oracle: design/engineering/code-conventions.md:54 模态覆盖层层级规则——新增覆盖层必须登记 z 序位置(登记序契约组) var list: Array = main.ui.flow.stack._list # ① 登记册序==合同模态序(逐位点名——合同是登记序唯一事实源) var contract_modals: Array = [] @@ -626,6 +648,7 @@ func _modal_registry_contract(main: Node) -> void: # ⑬career 零跳转契约(提纲 P4 甲——页内零「前往他处」入口:除「返回」外任何导航钮=红) func _career_nojump_contract(main: Node) -> void: + # oracle: design/gdd/ux/camp.md:19 生涯记录入口与返回钮口径由结构校验兜底——页内零导航入口(零跳转契约组) for nav in ["营地", "商店", "设置", "冒险", "继续上局", "退出", "前往"]: if ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, nav, "contains") != null: problems.append("[career][nojump] 页内出现导航入口「%s」(零跳转违约——生涯纯只读)" % nav) @@ -635,6 +658,7 @@ func _career_nojump_contract(main: Node) -> void: # (名行基线离盒底 1px 同型高危写法返修回归闸; # 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘) func _standeefit_contract(main: Node) -> void: + # oracle: design/gdd/ux/battle.md:154 主角站桩立绘(无框立绘+落影)——站桩内容矩形⊆舞台(站桩适配组) var st: Node = main.ui.battle.stage if st == null or st.hero_standee == null: problems.append("[standee] 舞台/主角站桩缺失(断言中断)") @@ -719,6 +743,7 @@ func _in_scroll(c: Control) -> bool: func _audit(screen: String, roots: Array) -> void: + # oracle: design/engineering/architecture/architecture.md:168 组合根交接面与呈现装配——屏结构审计宿主面(结构审计组) for r in roots: if r != null and is_instance_valid(r): _force_layout(r) From 3537f8e2a51cca508b8af0a559894882543e2b92 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:01:55 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=967/7=C2=B7ui=5Fclick=5Fprobe):=2037=E5=A4=84em?= =?UTF-8?q?it=E2=86=9236=E5=A4=84=E7=9C=9F=E8=BE=93=E5=85=A5+1=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E2=80=94=E2=80=94=E5=9C=BA=E6=99=AF=E7=83=AD=E7=82=B9?= =?UTF-8?q?(dummy/chest/board)=3Dpush=5Fclick=5Fat=E7=9C=9F=E7=82=B9,?= =?UTF-8?q?=E7=AA=97=E5=85=B3=E9=97=AD=3D=E7=9C=9F=E7=82=B9=E5=BA=95?= =?UTF-8?q?=E6=A0=8F=E8=BF=94=E5=9B=9E=E8=90=A5=E5=9C=B0=E9=92=AE,?= =?UTF-8?q?=E9=80=89=E6=A1=A3=E6=96=B0=E6=A1=A3/=E8=BF=9B=E6=A1=A3/?= =?UTF-8?q?=E5=88=A0=E6=A1=A3/=E7=A1=AE=E8=AE=A4=E5=8F=96=E6=B6=88/?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E8=BF=94=E5=9B=9E/=E7=94=9F=E6=B6=AF?= =?UTF-8?q?=E8=BF=94=E5=9B=9E/=E5=95=86=E5=BA=97=E7=A6=BB=E5=BC=80(?= =?UTF-8?q?=E7=9C=9F=E5=8F=91=E5=B0=84=E6=BA=90shop=5Foverlay=5Fview:66)/?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E4=B8=8E=E6=88=98=E6=96=97=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=A7=92=E6=A0=87/=E7=BB=A7=E7=BB=AD=E9=92=AE/=E4=B8=8B?= =?UTF-8?q?=E4=B8=80=E5=B1=82/=E7=BB=88=E5=B1=80=E7=BB=A7=E7=BB=AD?= =?UTF-8?q?=E4=B8=8E=E8=BF=94=E5=9B=9E/=E5=BC=83=E5=B1=80=E5=8F=8C?= =?UTF-8?q?=E5=87=BB(3s=E7=AA=97)/=E5=9B=BE=E4=BE=8B=E5=BC=80/=E5=81=9C?= =?UTF-8?q?=E6=89=8B=3D=E7=9C=9F=E7=82=B9push=5Fclick,=E6=A7=BD=E4=BD=8Dto?= =?UTF-8?q?oltip=3DInputEventMouseMotion=E7=9C=9F=E6=82=AC=E5=81=9C(enter/?= =?UTF-8?q?exited=E5=BC=95=E6=93=8E=E5=90=8C=E6=AD=A5=E6=B4=BE=E5=8F=91,?= =?UTF-8?q?=E7=A6=81await=E2=80=94=E2=80=94=E6=A7=BD=E4=BD=8D=E9=9A=8F?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E9=87=8D=E5=BB=BA=E8=B7=A8=E5=B8=A7=E5=8F=A5?= =?UTF-8?q?=E6=9F=84=E5=A4=B1=E6=95=88=E5=AE=9E=E8=AF=81);=201=E4=BF=9D?= =?UTF-8?q?=E7=95=99=3D:914=E7=A6=81=E7=94=A8=E6=80=81P1=E5=AE=88=E5=8D=AB?= =?UTF-8?q?(=E7=9C=9F=E7=82=B9=E8=A2=AB=E5=BC=95=E6=93=8E=E5=90=9E,?= =?UTF-8?q?=E5=AE=88=E5=8D=AB=E5=8F=AA=E8=83=BD=E4=BF=A1=E5=8F=B7=E6=B2=BF?= =?UTF-8?q?=E9=AA=8C,=E8=AE=A1=E5=88=92=E5=86=85=E5=A4=84=E7=BD=AE);=20?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E7=BA=AA=E5=BE=8B:refresh=5Fcamp=E9=87=8D?= =?UTF-8?q?=E5=BB=BA=E5=90=8E=E7=9C=9F=E7=82=B9=E5=89=8D=E4=BF=9D=E4=B8=89?= =?UTF-8?q?=E5=B8=A7(=E5=B8=A7=E5=B0=BEcall=5Fdeferred);=20=E6=9C=AC?= =?UTF-8?q?=E6=AD=A5=E5=87=AD=E6=8D=AE:=20ui=5Fclick=5Fprobe=20headless?= =?UTF-8?q?=E7=9B=B4=E8=B7=91exit=3D0=E9=9B=B6FAIL(PROBE=5FSEED=3D7?= =?UTF-8?q?=E5=85=A8=E8=B7=91=E6=89=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/ui_click_probe.gd | 98 ++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index f5f2233d..cae32cfd 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -67,7 +67,7 @@ func _ready() -> void: await get_tree().process_frame if not main.ui.modals.camp.scene.visible: _problems.append("营地场景根未打开(进档开层新路径)〔句柄子容器批 V1〕") - main.ui.modals.camp.scene.object_pressed.emit(&"dummy") # 场景点选真实路由(木桩→天赋树子界面——卡④ T5) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center()) # 场景点选真实路由(木桩→天赋树子界面——卡④ T5) await get_tree().process_frame if main.ui.modals.camp.tree._tree_buy_btns.is_empty(): _problems.append("树淬炼购买钮缺失(四柜台拆解后树=独立子界面)〔句柄子容器批 V1〕") @@ -75,7 +75,8 @@ func _ready() -> void: # 树钮句柄用后即取(refresh_camp 随刷重建树行板——旧钮 queue_free,跨刷新句柄必失效) iso_c.meta_state._add_keystone(9999) main.ui.modals.camp.tree.refresh_camp(main._camp_data()) - await get_tree().process_frame + for fi1 in 3: + await get_tree().process_frame # 重建行板排版系帧尾 call_deferred——真点前保三帧 var buy_btn: Button = null for card in main.ui.modals.camp.tree._tree_buy_btns: if String(card.node_id) == "sp_hp": @@ -85,7 +86,7 @@ func _ready() -> void: else: var bal_before: int = iso_c.meta_state.keystone_balance() var price: int = int(main.db.crossrun.meta_tree.row_by_id("sp_hp").get("price", 0)) - buy_btn.pressed.emit() # 合成购买点击 → _on_camp_tree_buy → meta.tree_buy(写调用唯一归组合根) + buy_btn.pressed.emit() # 接线验证:窗内写路径意图信号(能力行 160 登记入口=意图信号→presenter→tree_buy)——非可达性判据面;场景热点开合由真点承载 await get_tree().process_frame if int(iso_c.meta_state.keystone_balance()) != bal_before - price: _problems.append("营地树购买未出账(%d→%d 应 −%d)〔句柄子容器批 V1〕〔营地图批 V4〕" % [bal_before, iso_c.meta_state.keystone_balance(), price]) @@ -99,10 +100,11 @@ func _ready() -> void: if String(card.node_id) == "att_socket1": socket_btn = card.btn if socket_btn != null: - socket_btn.pressed.emit() + socket_btn.pressed.emit() # 接线验证:同上(写路径意图信号面) await get_tree().process_frame main.ui.modals.camp.tree.refresh_camp(main._camp_data()) - await get_tree().process_frame + for fi2 in 3: + await get_tree().process_frame # 重建行板排版系帧尾 call_deferred——真点前保三帧(单帧 rect 陈旧实证) var fork_btn: Button = null for card in main.ui.modals.camp.tree._tree_buy_btns: if String(card.node_id) == "fork_posture": @@ -110,10 +112,11 @@ func _ready() -> void: if fork_btn == null: _problems.append("Fork 解锁钮缺失(点击路径)〔句柄子容器批 V1〕") else: - fork_btn.pressed.emit() + fork_btn.pressed.emit() # 接线验证:同上 await get_tree().process_frame main.ui.modals.camp.tree.refresh_camp(main._camp_data()) - await get_tree().process_frame + for fi3 in 3: + await get_tree().process_frame # 同上——重建后三帧排版 var side_b: Button = null for fk in main.ui.modals.camp.tree._tree_fork_btns: if String(fk.group) == "fg_posture" and String(fk.side) == "b": @@ -121,7 +124,7 @@ func _ready() -> void: if side_b == null: _problems.append("Fork 双态切换钮缺失(解锁后应展开 a/b 钮)〔句柄子容器批 V1〕") else: - side_b.pressed.emit() # 合成切态点击 → _on_camp_fork_toggle → meta.tree_toggle_fork + side_b.pressed.emit() # 接线验证:切态意图信号 → _on_camp_fork_toggle → meta.tree_toggle_fork await get_tree().process_frame var iso_fk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json") iso_fk.load_from_disk() @@ -130,9 +133,9 @@ func _ready() -> void: # ---- 营地灰字取色断言(迭代36 wc4——四子界面拆分后按视图分扫;卡④ T5 迁移): # 图鉴锁文→codex 视图(锚随形态订正去『柜台』);秘宝行未解锁/同调形态/档位/刷新行→treasure 视图 # _workshop_holder 子树。font_color 逐处 == STONE_DEEP;树链段已闭树界面。 - main.ui.modals.camp.tree.closed.emit() # 树界面返回(关层归 presenter 单口) + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")) # 真点返回钮(关层归 presenter 单口) await get_tree().process_frame - main.ui.modals.camp.scene.object_pressed.emit(&"chest") # 宝箱→秘宝匣(灰字四源位宿主) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center()) # 宝箱→秘宝匣(灰字四源位宿主) await get_tree().process_frame var ws_labels: Array = [] _collect_labels_by_text(main.ui.modals.camp.treasure._workshop_holder, ws_labels) @@ -169,9 +172,9 @@ func _ready() -> void: _problems.append("营地灰字:刷新行未用 STONE_DEEP(wc4 :525)〔句柄子容器批 V1〕") if not refresh_inked: _problems.append("营地灰字:刷新行 label 未找到(wc4 定位锚失守)〔句柄子容器批 V1〕") - main.ui.modals.camp.treasure.closed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.treasure, "返回营地")) await get_tree().process_frame - main.ui.modals.camp.scene.object_pressed.emit(&"board") # 布告板→图鉴占位(锁文宿主) + ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"board").get_center()) # 布告板→图鉴占位(锁文宿主) await get_tree().process_frame var codex_labels: Array = [] _collect_labels_by_text(main.ui.modals.camp.codex, codex_labels) @@ -181,7 +184,7 @@ func _ready() -> void: for l in atlas_locks: if l.get_theme_color("font_color") != UiKit.STONE_DEEP: _problems.append("营地灰字:图鉴锁文未用 STONE_DEEP(wc4 :167)〔句柄子容器批 V1〕〔营地图批 V6〕") - main.ui.modals.camp.codex.closed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.codex, "返回营地")) await get_tree().process_frame main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3) await get_tree().process_frame @@ -213,7 +216,7 @@ func _ready() -> void: _problems.append("[savesel] 槽1 新档钮缺失(sl5/sl12 驱动中断)") else: var samples: Array = [] - new_btn.pressed.emit() # 真实按钮信号路径 → _on_save_select_new → create_slot+open_camp + ProbeUiUtil.push_click(main, new_btn) # 真点新档 → _on_save_select_new → create_slot+open_camp(open_camp_via_savesel:196 同款) for fi in 3: await get_tree().process_frame samples.append([main.ui.modals.menu.save_select_overlay.visible, main.ui.modals.camp.scene.visible, main.ui.screens.start_overlay.visible]) @@ -239,7 +242,7 @@ func _ready() -> void: if load_btn == null: _problems.append("[savesel] 槽2 进档钮缺失(sl6 驱动中断——夹具应有档)") else: - load_btn.pressed.emit() + ProbeUiUtil.push_click(main, load_btn) await get_tree().process_frame if main.save_manager.slots.active_slot() != 2: _problems.append("[savesel] sl6 进档链失守(active=%d 应=2——激活该槽)" % main.save_manager.slots.active_slot()) @@ -252,9 +255,9 @@ func _ready() -> void: await get_tree().process_frame var del_btn: Button = main.ui.modals.menu.save_select_overlay._delete_btns.get(2) if del_btn == null: - _problems.append("[savesel] 槽2 删档钮缺失(sl7 驱动中断)") + _problems.append("[savesel] 槽2 删档钮缺失(sl7 驱动中断)") else: - del_btn.pressed.emit() + ProbeUiUtil.push_click(main, del_btn) await get_tree().process_frame if not main.ui.modals.menu.confirm_dialog.is_open(): _problems.append("[savesel] sl7 一击未只开确认窗(直删形态)") @@ -265,7 +268,7 @@ func _ready() -> void: _problems.append("[savesel] sl7 确认窗未经模态栈开(直开旁路——wr2 单口失守)") elif not main.ui.flow.locks_input(): _problems.append("[savesel] sl7 确认窗开时输入锁未生效(锁域失守——wr2)") - main.ui.modals.menu.confirm_dialog.cancelled.emit() # 取消路径(真实信号——cancelled 钮同源) + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._cancel_btn) # 真点取消(cancelled 钮同源) await get_tree().process_frame if main.ui.modals.menu.confirm_dialog.is_open() or not FileAccess.file_exists(csel_tpl % 2): _problems.append("[savesel] sl7 取消路径失守(应只关窗、文件仍在)") @@ -273,9 +276,9 @@ func _ready() -> void: _problems.append("[savesel] sl8 取消后消费方状态量未清(分派面残留)") if main.ui.flow.stack._occluded.has(main.ui.modals.menu.confirm_dialog): _problems.append("[savesel] sl7 取消后确认窗遮挡账未清(关侧未走模态栈——wr2)") - del_btn.pressed.emit() # 再开窗走确认路径 + ProbeUiUtil.push_click(main, del_btn) # 再开窗走确认路径 await get_tree().process_frame - main.ui.modals.menu.confirm_dialog.confirmed.emit() # 确认路径(真实信号——confirmed 钮同源) + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 真点确认(confirmed 钮同源) await get_tree().process_frame if FileAccess.file_exists(csel_tpl % 2) or main.ui.modals.menu.confirm_dialog.is_open(): _problems.append("[savesel] sl7 确认路径失守(应删档+关窗)") @@ -343,7 +346,7 @@ func _ready() -> void: if settings_close == null: _problems.append("设置面返回按钮缺失") else: - settings_close.pressed.emit() + ProbeUiUtil.push_click(main, settings_close) await get_tree().process_frame if main.ui.modals.menu.settings_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("设置面关闭后未恢复开始层") @@ -377,7 +380,7 @@ func _ready() -> void: if career_close == null: _problems.append("生涯页返回按钮缺失") else: - career_close.pressed.emit() + ProbeUiUtil.push_click(main, career_close) await get_tree().process_frame if main.ui.modals.menu.career_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("生涯页关闭后未恢复开始层") @@ -463,7 +466,7 @@ func _ready() -> void: # 分派链序:商店层=选路页上的模态覆盖(停点呈现为 # level_screen+shop 双 visible)——必须先于 level_screen 分派,否则下层候选点击 # 空转烧尽预算(实证:lv2 商店停点双 visible 循环 12000 帧 done=false)。 - main.ui.modals.menu.shop_overlay.leave_pressed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")) # 真点离开商店(leave_pressed 真发射源——shop_overlay_view.gd:66) _seals_placed.clear() continue if main.ui.modals.run.level_screen.visible: @@ -476,12 +479,12 @@ func _ready() -> void: if mpb == null or not mpb.visible or mpb.disabled: _problems.append("地图屏状态角标缺失/未点亮/被置灰(P1 丁)") else: - mpb.pressed.emit() + ProbeUiUtil.push_click(main, mpb) if not main.ui.modals.run.status_panel.visible or not main.ui.modals.run.level_screen.visible: _problems.append("地图屏角标开状态面板失败或地图屏被误藏(P2 甲层级失守)") var rbtn2: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix") if rbtn2 != null: - rbtn2.pressed.emit() + ProbeUiUtil.push_click(main, rbtn2) if main.ui.modals.run.status_panel.visible or not main.ui.modals.run.level_screen.visible: _problems.append("状态面板关闭后地图屏态失守(恢复语义破)") # 地图屏加成帽换代断言(迭代36 卡A wa2——「特性」→「属性值提升」;帽 Label 常在树; @@ -519,7 +522,7 @@ func _ready() -> void: if next_btn == null: _problems.append("大奖励屏确认按钮缺失") else: - next_btn.pressed.emit() + ProbeUiUtil.push_click(main, next_btn) await get_tree().process_frame await get_tree().process_frame if main.run_scope.runner == null or int(main.run_scope.runner.level_row.get("level_index", -1)) != 2: @@ -540,7 +543,7 @@ func _ready() -> void: if main.ui.modals.run.end_overlay.verdict_text() == "": _problems.append("胜利结算框判定行缺失") _settlement_confirms += 1 - main.ui.modals.run.end_overlay._btn.pressed.emit() # 「下一节点」真实信号路径 + ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) # 真点「下一节点」 await get_tree().process_frame await get_tree().process_frame if main.run_scope.settlement_stop_timer.is_armed() or not main.ui.modals.run.level_screen.visible: @@ -555,7 +558,7 @@ func _ready() -> void: # (_adventure_mode)在真终局仍 true——会把真终局当中途面点掉返回键(实证)。 var _adv_running: bool = main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running" if main.run_scope.is_adventure and _adv_running: - main.ui.modals.run.end_overlay._btn.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) continue if not main.ui.flow.locks_input(): _problems.append("终局 overlay 打开时输入未锁定") @@ -612,7 +615,7 @@ func _ready() -> void: if main.ui.modals.run.end_overlay.is_open(): # 中途结算面(state=running)=点继续推进;真终局(settling)=断点段前提满足,收口。 if main.run_scope.is_adventure and main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running": - main.ui.modals.run.end_overlay._btn.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) else: break elif main.ui.modals.run.curio_overlay.visible: @@ -626,18 +629,18 @@ func _ready() -> void: elif main.ui.modals.run.node_overlay.visible: main.ui.modals.run.node_overlay.press_continue() elif main.ui.modals.menu.shop_overlay.visible: - main.ui.modals.menu.shop_overlay.leave_pressed.emit() + ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")) elif main.ui.screens.inter_overlay.visible: var next_btn2: Button = ProbeUiUtil.find_button(main.ui.screens.inter_overlay, "进入下一层", "prefix") if next_btn2 != null: - next_btn2.pressed.emit() + ProbeUiUtil.push_click(main, next_btn2) elif main.ui.modals.run.level_screen.visible: var abandon_btn: Button = ProbeUiUtil.find_button(main.ui.modals.run.level_screen, "放弃", "prefix") if abandon_btn != null: _closed_via_abandon = true - abandon_btn.pressed.emit() + ProbeUiUtil.push_click(main, abandon_btn) await get_tree().process_frame - abandon_btn.pressed.emit() # 二击确认(3s 窗内) + ProbeUiUtil.push_click(main, abandon_btn) # 二击确认(3s 窗内) for i in 10: await get_tree().process_frame break @@ -665,7 +668,7 @@ func _ready() -> void: if main.ui.modals.run.end_overlay._btn == null: _problems.append("断点段:终局面返回按钮缺失") else: - main.ui.modals.run.end_overlay._btn.pressed.emit() # 真实按钮信号路径(_btn=继续/返回共用句柄) + ProbeUiUtil.push_click(main, main.ui.modals.run.end_overlay._btn) # 真点返回(_btn=继续/返回共用句柄) await get_tree().process_frame if not main.ui.screens.start_overlay.visible: # 豁免:放弃路径收口时终局「返回」后仍挂三选一/关卡推进残留态(停点链), @@ -693,7 +696,7 @@ func _ready() -> void: _problems.append("大门无断点:未确认即进冒险〔营地图批 V3〕") if main.ui.modals.menu.confirm_dialog._secondary_btn.visible: _problems.append("大门无断点:次钮应隐藏(两出口形态)〔营地图批 V3〕") - main.ui.modals.menu.confirm_dialog.cancelled.emit() + ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._cancel_btn) # 真点取消(大门取消分支) await get_tree().process_frame if not main.ui.modals.camp.scene.visible or main.run_scope.is_adventure or main._camp_data()["balance"] != bal_gate: _problems.append("大门取消:未无变更退回营地(写口零调用/营地根 visible 失守)〔营地图批 V3〕") @@ -821,7 +824,7 @@ func _run_modal_probes_once() -> void: # ① 图例模态(pattern 6):打开 → 遮挡物隐藏 + 输入锁定;关闭 → 恢复 if not _tested_legend: _tested_legend = true - main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_legend) if not main.ui.modals.run.legend_overlay.is_open(): _problems.append("图例打开失败") if main.ui.modals.run.end_overlay.is_open() or main.ui.screens.start_overlay.visible: @@ -851,7 +854,7 @@ func _run_modal_probes_once() -> void: _problems.append("战斗屏状态角标缺失/未点亮/被置灰(P1 丁入口失守 visible=%s disabled=%s)" % [str(pbtn != null and pbtn.visible), str(pbtn != null and pbtn.disabled)]) else: - pbtn.pressed.emit() # 真实按钮信号路径 → 组合根 _on_status_badge_pressed + ProbeUiUtil.push_click(main, pbtn) # 真点角标 → 组合根 _on_status_badge_pressed if not main.ui.modals.run.status_panel.visible: _problems.append("状态面板未随角标打开(组合根接线失守)") if not main.ui.flow.locks_input(): @@ -903,12 +906,12 @@ func _run_modal_probes_once() -> void: if resume_btn == null: _problems.append("状态面板关闭钮缺失(「继续」)") else: - resume_btn.pressed.emit() + ProbeUiUtil.push_click(main, resume_btn) if main.ui.modals.run.status_panel.visible or main.ui.flow.locks_input(): _problems.append("状态面板关闭后未恢复(visible=%s locked=%s——R3 残留)" % [str(main.ui.modals.run.status_panel.visible), str(main.ui.flow.locks_input())]) # P1 守卫:图例开着 → 两处入口角标置灰;置灰态点击被处理器兜住(探针 emit 绕过 disabled) - main.ui.battle.board_zone.ap_bar._btn_legend.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_legend) if not main.ui.battle.board_zone.status_badge.disabled: _problems.append("图例开着时战斗屏状态角标未置灰(P1 守卫失守)") main.ui.battle.board_zone.status_badge.pressed.emit() @@ -917,16 +920,27 @@ func _run_modal_probes_once() -> void: main.ui.modals.run.legend_overlay.close() # ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发 if not _tested_tooltip: + if main.ui.modals.run.legend_overlay.is_open(): + main.ui.modals.run.legend_overlay.close() # tooltip 真悬停前提——P1 守卫段遗留开态图例盖住槽域(旧 emit 直发不受遮挡;真输入须净态) + await get_tree().process_frame var row = main.ui.battle.stage.hero_standee.slot_row # 状态 tooltip 载体(B 区站桩) for child in row.get_children(): if child is PanelContainer and child.visible and child.has_meta("eff"): _tested_tooltip = true - child.mouse_entered.emit() + var slot_pos: Vector2 = (child as Control).get_global_rect().get_center() + var ev_in := InputEventMouseMotion.new() + ev_in.position = slot_pos + ev_in.global_position = slot_pos + main.get_viewport().push_input(ev_in, true) # 真实悬停进入(motion 经输入管线——mouse_entered 在 push_input 内同步派发;禁 await——槽位随结算重建,跨帧句柄必失效) if not row._tip.visible: _problems.append("状态槽 tooltip 未显示") elif not row._tip_label.text.contains("来源") or not row._tip_label.text.contains("剩余"): _problems.append("状态槽 tooltip 缺来源/剩余(%s)" % row._tip_label.text.replace("\n", "|")) - child.mouse_exited.emit() + var ev_out := InputEventMouseMotion.new() + var out_pos: Vector2 = slot_pos + Vector2(400.0, 0.0) # 移出槽域 + ev_out.position = out_pos + ev_out.global_position = out_pos + main.get_viewport().push_input(ev_out, true) if row._tip.visible: _problems.append("状态槽 tooltip 未随移出隐藏") break @@ -1028,7 +1042,7 @@ func _random_unknown_adjacent(minesweeper) -> Vector2i: func _click_stop() -> void: # 停手走真实按钮信号路径(AP 条 → 组合根 settle_batch——排雷玩法 D1×6① 回归载体) - main.ui.battle.board_zone.ap_bar._btn_stop.pressed.emit() + ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_stop) # ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ---- From 157fb62a17429e4a7a373b1d199dc1afca45f5c4 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:05:39 +0800 Subject: [PATCH 12/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B01/7=C2=B7data=5Fconfig):=20?= =?UTF-8?q?12=E5=A5=97=E4=BB=B640=E5=87=BD=E6=95=B0=E9=80=90=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=8C=82#=20oracle=E6=B3=A8=E8=AE=B0=E2=80=94?= =?UTF-8?q?=E2=80=94=E8=A1=8C=E4=B8=BA=E5=A5=97=E4=BB=B6=E9=94=9Aowner?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E9=AA=8C=E6=94=B6=E6=A0=87=E5=87=86=E5=8F=A5?= =?UTF-8?q?(=E6=95=B0=E6=8D=AE=E9=85=8D=E7=BD=AE:198-204=E8=A3=85=E8=BD=BD?= =?UTF-8?q?/=E6=9F=A5=E8=A1=8C/=E9=BB=84=E9=87=91=E5=90=91=E9=87=8F/?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8/=E8=B6=8A=E5=9F=9F+=E9=94=BB?= =?UTF-8?q?=E9=80=A0=E5=B7=A5=E5=9D=8A:219/220=E5=88=87=E6=8D=A2=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E4=B8=8E=E4=BB=B7=E6=A0=BC=E6=A2=AF+=E5=85=B3?= =?UTF-8?q?=E5=8D=A1=E8=8A=82=E7=82=B9=E5=9B=BE=E7=94=9F=E6=88=90:127+?= =?UTF-8?q?=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82:153+=E5=86=92=E9=99=A9?= =?UTF-8?q?=E7=BB=93=E6=9E=84:281+=E9=9A=BE=E5=BA=A6=E6=9B=B2=E7=BA=BF:316?= =?UTF-8?q?+=E5=85=83=E8=BF=9B=E5=BA=A6:148+architecture:89/90=E8=AF=8D?= =?UTF-8?q?=E8=A1=A8=E4=B8=8ERNG),=E8=B4=9F=E9=9D=A2=E5=A5=97=E4=BB=B6?= =?UTF-8?q?=E9=94=9A=E6=95=B0=E6=8D=AE=E9=85=8D=E7=BD=AE:199/200=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=B1=BB=E5=88=AB=E4=B8=8E=E8=87=B4=E5=91=BD=E9=80=9A?= =?UTF-8?q?=E9=81=93;=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=206=E6=AD=A3?= =?UTF-8?q?=E5=A5=97=E4=BB=B6exit=3D0+5=E8=B4=9F=E5=A5=97=E4=BB=B6exit=3D1?= =?UTF-8?q?(=E5=9C=A8=E5=86=8C=E6=9C=9F=E6=9C=9B=E7=A0=81,=E7=9B=B4?= =?UTF-8?q?=E8=B7=9112/12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/unit/data_config/config_db_fatal_oob_test.gd | 2 ++ game/tests/unit/data_config/config_db_test.gd | 7 +++++++ game/tests/unit/data_config/counter_prefix_fatal_test.gd | 2 ++ game/tests/unit/data_config/effect_kind_retired_test.gd | 2 ++ game/tests/unit/data_config/forge_table_test.gd | 5 +++++ game/tests/unit/data_config/level_gen_params_test.gd | 4 ++++ .../unit/data_config/reward_dangling_treasure_test.gd | 2 ++ game/tests/unit/data_config/rng_service_test.gd | 5 +++++ game/tests/unit/data_config/rng_stream_boot_test.gd | 1 + game/tests/unit/data_config/table_structure_guard_test.gd | 8 ++++++++ .../unit/data_config/treasure_target_violation_test.gd | 2 ++ 11 files changed, 40 insertions(+) diff --git a/game/tests/unit/data_config/config_db_fatal_oob_test.gd b/game/tests/unit/data_config/config_db_fatal_oob_test.gd index 4fde9997..b67e2f72 100644 --- a/game/tests/unit/data_config/config_db_fatal_oob_test.gd +++ b/game/tests/unit/data_config/config_db_fatal_oob_test.gd @@ -12,6 +12,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:正式通道加载进程非 0 退出(装载前提组) _db = ConfigDBScript.new() var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: @@ -23,6 +24,7 @@ func _init() -> void: func _trigger_oob() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:204 越域请求在进入解析前致命中止(Fatal 级诊断+进程非 0,无兜底)(越域组) # 共享 .tres 上的 resolver 归最后装载的实例——autoload ConfigDB 的 _ready 在测试 _init 之后 # 二次注入覆盖私有实例,故触发前把解析通道归还被测实例(诊断落 _db,退出码判据才成立) _db.combat.board_gen._resolver = _db._resolver diff --git a/game/tests/unit/data_config/config_db_test.gd b/game/tests/unit/data_config/config_db_test.gd index d40a908a..670dc200 100644 --- a/game/tests/unit/data_config/config_db_test.gd +++ b/game/tests/unit/data_config/config_db_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 干净检出:表清单内全部表加载成功、就绪态断言(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕", load_ok and db.diagnostics_count("error") == 0) @@ -30,6 +31,7 @@ func _init() -> void: # ---- 进度坐标 ceil 解析(R1:向上下取整保证偏难;越最大行 clamp 取最大值 + 警告去重)---- func _test_progress_resolution(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:203 向上取整解析:请求无精确行返回不低于请求坐标的最近行(进度查行组) var bg_rows: Array = db.combat.board_gen.rows # 行键全集自表推导(禁写死关卡/深度数值) var level_keys: Array = [] @@ -86,6 +88,7 @@ func _test_progress_resolution(db) -> void: # ---- 变异池门控(unlock_level 闸 + ∅ board_types 通配 + 节点身份第三维)---- func _test_variant_pool_gate(db) -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:153 候选池空合法:门控筛空时静默零布置(变异门控组) var rows: Array = db.combat.variation.rows var gated_row := {} var wildcard_row := {} @@ -128,6 +131,7 @@ func _rows_contain(pool: Array, row: Dictionary) -> bool: # ---- ShopTable 消费面(行集解析单点——runner/编排禁自扫表的契约消费侧验证)---- func _test_shop_accessors(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行结构与引用闭合(商店访问器行结构组) var products: Array = db.run.shop.product_rows() if products.is_empty(): Kit.check("ShopTable products 行族非空(表已已入库前提)", false) @@ -155,6 +159,7 @@ func _test_shop_accessors(db) -> void: # ---- MetaTable 消费面 ---- func _test_meta_accessors(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行结构与引用闭合(元进度访问器行结构组) # 无 forge_products 行族断言(行族/访问器不在表,V20 清单) var key_rows: Array = db.crossrun.meta.boss_keystone if not key_rows.is_empty(): @@ -174,6 +179,7 @@ func _test_meta_accessors(db) -> void: # ---- TABLE_MANIFEST ↔ data/tables 目录同步(各系统→D2 单点契约的登记面)---- func _test_manifest_dir_sync(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 表目录无清单外文件(清单对账组) var manifest: Dictionary = db.TABLE_MANIFEST var dir := DirAccess.open("res://data/tables") var disk_files: Array = [] @@ -198,6 +204,7 @@ func _test_manifest_dir_sync(db) -> void: # (rowset_rule——行序即契约,R6 加载期兜底)③行集实例总数 ≤4(骰后值运行面,spawn 守卫——冒烟侧验证)。 func _test_rowset_discipline() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别:重复键与缺锚行逐类断言(行集纪律组) # 行集用 rows.assign([...]) 而非直接赋值:rows 是 Array[Dictionary] 定型属性,直接赋 untyped # 数组字面量会触发「Invalid assignment」脚本错误——该错误不改退出码,却会**中断本函数剩余断言** #(本函数五条纪律断言曾因此全部静默不跑,套件仍报 all green,是零覆盖假象)。 diff --git a/game/tests/unit/data_config/counter_prefix_fatal_test.gd b/game/tests/unit/data_config/counter_prefix_fatal_test.gd index 6f0e1951..6b09280e 100644 --- a/game/tests/unit/data_config/counter_prefix_fatal_test.gd +++ b/game/tests/unit/data_config/counter_prefix_fatal_test.gd @@ -20,6 +20,7 @@ const PROBE_REF := ' {\n "feat_id": "ref_probe",\n "display_name": "引用探 func _inject(dir: String, probe: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:前缀违规夹具注入(前缀夹具组) var path := dir + "/feats.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -44,6 +45,7 @@ func _inject(dir: String, probe: String) -> bool: func _assert_rejected(bad_dir: String, probe: String, label: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:违规表拒绝且进程非 0(拒绝判定组) # 返回 true=正确拒绝;false=放行或夹具失败(调用方据此退出 2) if not Kit.copy_tables(SRC_DIR, bad_dir) or not _inject(bad_dir, probe): push_error("[%s] 夹具目录准备失败" % label) diff --git a/game/tests/unit/data_config/effect_kind_retired_test.gd b/game/tests/unit/data_config/effect_kind_retired_test.gd index a3b46a29..b3476417 100644 --- a/game/tests/unit/data_config/effect_kind_retired_test.gd +++ b/game/tests/unit/data_config/effect_kind_retired_test.gd @@ -39,6 +39,7 @@ const PROBE_TRADE_KEY := ' { func _inject(dir: String, probe: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:退役词夹具注入(退役词夹具组) var path := dir + "/feats.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -63,6 +64,7 @@ func _inject(dir: String, probe: String) -> bool: func _assert_rejected(bad_dir: String, probe: String, label: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:退役词加载致命中止(拒绝判定组) # 返回 true=正确拒绝;false=放行或夹具失败(调用方据此退出 2) if not Kit.copy_tables(SRC_DIR, bad_dir) or not _inject(bad_dir, probe): push_error("[%s] 夹具目录准备失败" % label) diff --git a/game/tests/unit/data_config/forge_table_test.gd b/game/tests/unit/data_config/forge_table_test.gd index e26e7c61..f4d4bc34 100644 --- a/game/tests/unit/data_config/forge_table_test.gd +++ b/game/tests/unit/data_config/forge_table_test.gd @@ -12,6 +12,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 锻造表装载与就绪态(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕", load_ok and db.diagnostics_count("error") == 0) @@ -26,6 +27,7 @@ func _init() -> void: # ---- 生产表六行正向(V1 生产面 + V11 行字段闭集)---- func _test_production_rows(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行 schema 正反逐类断言(生产行结构组) var rows: Array = db.crossrun.forge.route_rows() Kit.check("六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕", rows.size() == 6) var ids := [] @@ -89,6 +91,7 @@ func _test_production_rows(db) -> void: # ---- 几何价格梯(V2 价格面单测半边——购买写在 T3 冒烟)---- func _test_price_ladder(db) -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 定价结构:每级价格随已购级数几何递增、基价与公比读表单点(价格梯组) var row: Dictionary = db.crossrun.forge.route("sword") var prices := [] for owned in range(5): @@ -147,6 +150,7 @@ func _mut_spear_initial(t) -> void: func _test_negative_cases() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖(负向 schema 组) # 夹具本身须零违例(否则负向判读失效) var t_ok = ForgeTableScript.new() t_ok.routes = _base_rows() @@ -215,6 +219,7 @@ func _test_negative_cases() -> void: # 32→31:改写族清退批摘 spike(CI run 87 追绿——词表合法收窄的断言随动)。 func _test_effect_kinds_closed() -> void: +# oracle: design/engineering/architecture/architecture.md:89 事件/事实/效果类型/算子四词表封闭(特效闭集组) Kit.check("效果引擎效果类型闭集 31 种(甲案词表退役 overflow/attack_segments+改写族清退摘 spike)〔锻造工坊批 V11〕", EffectEngineScript.EFFECT_KINDS.size() == 31) Kit.check("效果引擎事件词表在册 21 种(charge_full 位摘名——蓄力层删键批)〔锻造工坊批 V11〕", diff --git a/game/tests/unit/data_config/level_gen_params_test.gd b/game/tests/unit/data_config/level_gen_params_test.gd index 116cee43..cd87446a 100644 --- a/game/tests/unit/data_config/level_gen_params_test.gd +++ b/game/tests/unit/data_config/level_gen_params_test.gd @@ -11,6 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 参数档表装载与就绪态(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕", @@ -42,6 +43,7 @@ func _table(rows: Array) -> Variant: # ---- 生产三行正向(V2)---- func _test_production(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:127 产出合格:任意参数档产出的图必过产出约束(生产档组) var r1: Dictionary = db.run.level_gen_params.row_by_level_index(1) var r2: Dictionary = db.run.level_gen_params.row_by_level_index(2) var r3: Dictionary = db.run.level_gen_params.row_by_level_index(3) @@ -59,6 +61,7 @@ func _test_production(db) -> void: # ---- 访问器缺行防御 ---- func _test_accessor() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:127 参数档访问器读回与产出约束(访问器组) var t = _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4)]) Kit.check("row_by_level_index 缺行返回空字典〔fv2〕", t.row_by_level_index(9).is_empty()) @@ -66,6 +69,7 @@ func _test_accessor() -> void: # ---- 域校验逐类负向(V2)---- func _test_negative() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:参数档 schema 拒绝(负向组) # 合法基线三行 var base := [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4), _row(2, 9, 0.5, [0.5, 0.2, 0.15, 0.15], 2, 2, 6), diff --git a/game/tests/unit/data_config/reward_dangling_treasure_test.gd b/game/tests/unit/data_config/reward_dangling_treasure_test.gd index 83102d2a..5f7d785a 100644 --- a/game/tests/unit/data_config/reward_dangling_treasure_test.gd +++ b/game/tests/unit/data_config/reward_dangling_treasure_test.gd @@ -14,6 +14,7 @@ const BAD_DIR := "user://treasure_fixture_dangling" func _inject_violation() -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别:悬空引用夹具(悬空引用组) var path := BAD_DIR + "/reward.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -36,6 +37,7 @@ func _inject_violation() -> bool: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:悬空引用被拒绝、进程非 0(拒绝组) if not Kit.copy_tables(SRC_DIR, BAD_DIR) or not _inject_violation(): push_error("[reward_dangling_treasure_test] 夹具目录准备失败") quit(2) diff --git a/game/tests/unit/data_config/rng_service_test.gd b/game/tests/unit/data_config/rng_service_test.gd index a7ce3bfe..b2c31fd6 100644 --- a/game/tests/unit/data_config/rng_service_test.gd +++ b/game/tests/unit/data_config/rng_service_test.gd @@ -12,6 +12,7 @@ const TEST_SEED := 20260928 # 夹具种子(任意固定值;语义 = 可复 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 黄金向量:登记向量逐个复算(装载组) _test_derive_determinism() _test_counter_ledger() _test_stream_draw_determinism() @@ -27,6 +28,7 @@ func _fresh_service(): # 单盐派生纯函数:两全新实例同种子逐位一致(derive_at 不推计数器——纯函数契约) func _test_derive_determinism() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 黄金向量:随机派生纯函数对登记向量逐个复算相等、同输入恒同输出(派生确定性组) var a = _fresh_service() var b = _fresh_service() var same := true @@ -42,6 +44,7 @@ func _test_derive_determinism() -> void: # 全局取数计数器:唯一账本——open_stream 逐次 +1(scope 单调) func _test_counter_ledger() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 计数器纪律:连续取号跨模拟关卡切换单调不重置(计数账本组) var rng = _fresh_service() var c0: int = rng.counter() rng.open_stream("armor_roll") @@ -59,6 +62,7 @@ func _test_counter_ledger() -> void: # 同种子两实例同标签流的实际取数序列逐位一致(回放确定性契约——存档快照=主种子+计数器的前提) func _test_stream_draw_determinism() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 同输入恒同输出:流取号确定性(流取号组) var faces := 20 # d20 探针面数(夹具值,非玩法表值——只用于产生可比较序列) var a = _fresh_service() var b = _fresh_service() @@ -73,6 +77,7 @@ func _test_stream_draw_determinism() -> void: # 负输入 abs 归一化(警告通道)——|−s| 与 s 同派生 func _test_negative_seed_normalization() -> void: +# oracle: design/engineering/architecture/architecture.md:90 RngService:master_seed 与各流 scope 计数器单点(种子规约组) var pos = _fresh_service() var neg = RngServiceScript.new() neg.set_master_seed(-TEST_SEED) diff --git a/game/tests/unit/data_config/rng_stream_boot_test.gd b/game/tests/unit/data_config/rng_stream_boot_test.gd index 1943c7e9..3c9a4953 100644 --- a/game/tests/unit/data_config/rng_stream_boot_test.gd +++ b/game/tests/unit/data_config/rng_stream_boot_test.gd @@ -22,6 +22,7 @@ func _init() -> void: # 单元宿主改由 `_init` 显式调用,断言逐字未动;源 :70 事件日志落盘条经主会话裁决一 # 改判集成测试——不随本宿主,见迁移账本转办清单)---- func _smoke_initialize() -> void: +# oracle: design/engineering/architecture/architecture.md:90 RngService autoload:open_stream 有状态门面与启动初始化(启动流组) print("=== Dungeon Sweeper 垂直切片冒烟 ===") var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) 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 7f927de2..e52a6cbf 100644 --- a/game/tests/unit/data_config/table_structure_guard_test.gd +++ b/game/tests/unit/data_config/table_structure_guard_test.gd @@ -29,6 +29,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 表结构守卫装载与就绪态(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(沿被迁断言面的源语义:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -91,6 +92,7 @@ func _first_encounter_band(level_index: int, enemy_id: String) -> int: # ① 冒险表(单行全局 Rule 4 豁免——建表与接线闸;TP0 令牌池:配额结构/件值对/池总量域检) func _smoke_adventure_table() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:281 冒险结构表校验(冒险表组) print("--- 冒险表(AdventureTable——令牌池行值单点:回血/配额/件值对/同调位)---") Kit.check("db.adventure 已加载(adventure.tres 入 manifest)", _db.run.adventure != null) if _db.run.adventure == null: @@ -209,6 +211,7 @@ func _smoke_adventure_table() -> void: # ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:29 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单)---- func _smoke_enemy_bands() -> void: +# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度与敌人带校验(敌人带组) print("--- 敌人表深度带键结构(29 行键序 · (1,3) 混编 · Boss 带键覆盖表)---") # ① 29 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前) # 迭代 35 生成图深度扩面覆盖批随动:战斗带顶扩到各关最大生成深度(L1→6/L2→9/L3→13)、 @@ -297,6 +300,7 @@ func _smoke_enemy_bands() -> void: # ④ 掷点参数域:档位边界升序/档位值降序无零档/骰面与暴击参数在域(加载校验双保险)。 func _smoke_ac_swap_guard() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219 切换语义:一次冒险=一个武器类型+一个甲型,冒险开始后锁死(AC 换装守卫组) print("--- 护甲等级掷点批·换装护栏 ---") # ① 三处无双轨 Kit.check("状态表 defense_up 行已退役(无双轨)", _db.combat.status_effects.row_by_id("defense_up").is_empty()) @@ -415,6 +419,7 @@ func _smoke_ac_swap_guard() -> void: # ---- 地形变异 框架面(四行两轴×两关 + 门控 + 跨字段互锁)---- func _smoke_variation_framework() -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:153 候选池门控与静默零布置(变异框架组) print("--- 地形变异 框架面(首批四类型 + 候选池门控 + 跨字段互锁)---") Kit.check("变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)", _db.combat.variation.rows.size() == 6) @@ -450,6 +455,7 @@ func _smoke_variation_framework() -> void: # ---- 难度曲线与Boss DifficultyTable 结构位冒烟(AC-10:空表零行为 + 套乘点 = 行值快照前)---- func _smoke_difficulty_table() -> void: +# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度修饰套乘(难度表组) print("--- 难度曲线与Boss·DifficultyTable 结构位 ---") Kit.check("DifficultyTable MVP 空表加载(结构位)", _db.run.difficulty != null and _db.run.difficulty.rows.is_empty()) # ① 空表零行为:敌人系统.spawn 敌值 = EnemyTable 行值原样 @@ -480,6 +486,7 @@ func _smoke_difficulty_table() -> void: # ---- ⑤ 格面资源九型 + gp_drop 流(黄金向量)---- func _smoke_nine_types_gp_stream() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:九型 GP 流校验(九型流组) print("--- 格面资源九型 + gp_drop 流(奖励字典 18 格 + 黄金向量)---") Kit.check("格面资源十型封闭(第九型 gp 九型批 2026-09-20 / 第十型 treasure_item 秘宝内容批 2026-09-29——词表 v1.2)", RewardTable.CELL_TYPES.size() == 10 and RewardTable.CELL_TYPES.has("gp") and RewardTable.CELL_TYPES.has("treasure_item")) @@ -529,6 +536,7 @@ func _smoke_nine_types_gp_stream() -> void: # ---- 元进度·MetaTable 表结构(行值全 provisional 归元进度设计文档配读数; # conversion 覆盖恰三,Boss 两档 first ≥ repeat)---- func _smoke_meta() -> void: +# oracle: design/gdd/systems/跨局循环/元进度.md:148 元进度行族校验(meta 行族组) print("--- 元进度·MetaTable 表结构 ---") var meta_tbl = _db.crossrun.meta Kit.check("MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)", diff --git a/game/tests/unit/data_config/treasure_target_violation_test.gd b/game/tests/unit/data_config/treasure_target_violation_test.gd index f644ac16..99ba2573 100644 --- a/game/tests/unit/data_config/treasure_target_violation_test.gd +++ b/game/tests/unit/data_config/treasure_target_violation_test.gd @@ -15,6 +15,7 @@ const BAD_DIR := "user://treasure_fixture_target_violation" func _inject_violation() -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:违规夹具逐类注入(真视窄域夹具组) var path := BAD_DIR + "/treasure_items.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -39,6 +40,7 @@ func _inject_violation() -> bool: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:违规表被拒绝、进程非 0(违规拒绝组) if not Kit.copy_tables(SRC_DIR, BAD_DIR) or not _inject_violation(): push_error("[treasure_target_violation_test] 夹具目录准备失败") quit(2) From ddc352a98532fd832bd347e4096184c6bef4e900 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:15:54 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B02/7=C2=B7core):=2034?= =?UTF-8?q?=E5=A5=97=E4=BB=B6135=E5=87=BD=E6=95=B0=E9=80=90=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=8C=82#=20oracle=E6=B3=A8=E8=AE=B0=E2=80=94?= =?UTF-8?q?=E2=80=94=E8=A1=8C=E4=B8=BA=E5=A5=97=E4=BB=B6=E9=94=9Aowner?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E9=AA=8C=E6=94=B6=E5=8F=A5(=E5=86=92?= =?UTF-8?q?=E9=99=A9=E7=BB=93=E6=9E=84:283-287/=E5=85=B3=E5=8D=A1=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=9C=B0=E5=9B=BE:176-184/=E6=A3=8B=E7=9B=98=E7=94=9F?= =?UTF-8?q?=E6=88=90:221-229/=E6=8E=92=E9=9B=B7=E7=8E=A9=E6=B3=95:228-238/?= =?UTF-8?q?=E8=A1=8C=E5=8A=A8=E7=BB=8F=E6=B5=8E/=E6=88=98=E6=96=97?= =?UTF-8?q?=E7=BB=93=E7=AE=97:362/=E5=9C=B0=E5=9B=BE=E6=88=98=E6=96=97?= =?UTF-8?q?=E8=B5=84=E6=BA=90/=E7=A7=98=E5=AE=9D/=E5=A4=A9=E8=B5=8B?= =?UTF-8?q?=E6=A0=91:192-199/=E9=94=BB=E9=80=A0=E5=B7=A5=E5=9D=8A:215-220/?= =?UTF-8?q?=E5=85=83=E8=BF=9B=E5=BA=A6/=E5=AD=98=E6=A1=A3=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96:61-181/=E6=95=B0=E6=8D=AE=E9=85=8D=E7=BD=AE:?= =?UTF-8?q?199-204/=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82:153/=E5=A5=87?= =?UTF-8?q?=E7=89=A9/=E5=85=B3=E5=8D=A1=E8=8A=82=E7=82=B9=E5=9B=BE?= =?UTF-8?q?=E7=94=9F=E6=88=90:127-132),=E6=83=AF=E4=BE=8B=E4=B8=8E?= =?UTF-8?q?=E5=B0=81=E8=A3=85=E5=A5=97=E4=BB=B6=E9=94=9Acode-conventions:4?= =?UTF-8?q?0/41=E4=B8=8Earchitecture:89/103/107/144;=20=E6=9C=AC=E6=AD=A5?= =?UTF-8?q?=E5=87=AD=E6=8D=AE:=2033=E5=A5=97=E4=BB=B6exit=3D0+run=5Ftime?= =?UTF-8?q?=5Ffatal=3D1(=E5=9C=A8=E5=86=8C=E8=B4=9F=E7=A0=81)=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E7=9B=B4=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/unit/core/adventure_feats_test.gd | 4 ++++ game/tests/unit/core/auto_player_deduce_test.gd | 1 + game/tests/unit/core/battle_driver_drive_test.gd | 4 ++++ game/tests/unit/core/board_gen_reward_test.gd | 5 +++++ game/tests/unit/core/board_generator_test.gd | 7 +++++++ game/tests/unit/core/breakpoint_resume_test.gd | 1 + .../unit/core/config_db_cross_validator_test.gd | 4 ++++ game/tests/unit/core/config_db_resolver_test.gd | 7 +++++++ game/tests/unit/core/curio_refresh_paid_test.gd | 1 + game/tests/unit/core/effect_words_t1_test.gd | 1 + game/tests/unit/core/encapsulation_p2_sweep_test.gd | 6 ++++++ game/tests/unit/core/encapsulation_sweep_test.gd | 10 ++++++++++ game/tests/unit/core/feat_vouchers_test.gd | 1 + game/tests/unit/core/hero_ledger_test.gd | 1 + game/tests/unit/core/level_map_type_vis_test.gd | 8 ++++++++ game/tests/unit/core/level_shop_economy_test.gd | 13 +++++++++++++ game/tests/unit/core/level_table_validate_test.gd | 4 ++++ game/tests/unit/core/meta_progression_forge_test.gd | 11 +++++++++++ game/tests/unit/core/minesweeper_rules_test.gd | 7 +++++++ game/tests/unit/core/node_map_generator_test.gd | 10 ++++++++++ game/tests/unit/core/node_map_growth_test.gd | 3 +++ game/tests/unit/core/playtest_logger_guard_test.gd | 1 + game/tests/unit/core/pool_case_d_test.gd | 5 +++++ game/tests/unit/core/run_time_fatal_test.gd | 1 + game/tests/unit/core/save_boot_no_activate_test.gd | 4 ++++ game/tests/unit/core/save_manager_domain_test.gd | 2 ++ game/tests/unit/core/save_slot_isolation_test.gd | 3 +++ game/tests/unit/core/save_slot_summary_test.gd | 3 +++ game/tests/unit/core/settle_segments_test.gd | 1 + game/tests/unit/core/treasure_effects_test.gd | 1 + game/tests/unit/core/treasure_items_test.gd | 1 + game/tests/unit/core/tree_requires_level_test.gd | 1 + game/tests/unit/core/tree_rules_v11_test.gd | 2 ++ 33 files changed, 134 insertions(+) diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index 8d78c4c6..623a42e7 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -17,6 +17,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:281 冒险结构与专长宿主(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -58,6 +59,7 @@ func _hits_kind(hits: Array, kind: String) -> Array: # ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态) func _smoke_adventure_api() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:283(冒险 API 组:开始受理/序列/回调) print("--- 冒险结构 API 闸(重复开始拒绝 · 入口停点 · 放弃两态受理)---") var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(42)) adr.set_hero_override(900, 400) # 首战速胜档(放弃的非战斗受理要走到选路态) @@ -120,6 +122,7 @@ func _api_mark_node(_node: Dictionary, _info: Dictionary, seq: Array) -> void: # ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配冒险成功/种子复现/大奖励/败北线/ # 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃) func _smoke_adventure() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:284(冒险生命周期组:推进/末关/倒下/重开/生命账) print("--- 冒险结构验收(三关序列 · 大奖励 · 摘要五件事 · 账本封闭)---") var autoplay = load("res://sim/level_autoplay.gd") # ① 表与覆盖义务(序列关数 = 行集关数;节点源 = 参数档在固定种子下的生成图, @@ -460,6 +463,7 @@ func _smoke_adventure() -> void: # ---- 专长冒烟(feats 四级 · 案D 池接线 · V3 订阅面逐件三列)---- func _smoke_feats() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(奇物授予与凭证保底组) print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---") # ① 现行表牌库构成:37 件全量入池——102 数值令牌+37 relic 令牌(四级 rarity 直映射) Kit.check("feat_table 已加载(37 件=基础 27+迁入 2+完美保险 1+迭代 46 换图系七件——换代)", _db.crossrun.feats != null and (_db.crossrun.feats.rows as Array).size() == 37) diff --git a/game/tests/unit/core/auto_player_deduce_test.gd b/game/tests/unit/core/auto_player_deduce_test.gd index 380e7865..f9dfcb0b 100644 --- a/game/tests/unit/core/auto_player_deduce_test.gd +++ b/game/tests/unit/core/auto_player_deduce_test.gd @@ -16,6 +16,7 @@ func _init() -> void: func _smoke_zero_chain_rule() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230(0 格连锁传播闭包——模拟玩家算子组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 Kit.check("前置:表加载零 error", load_ok) diff --git a/game/tests/unit/core/battle_driver_drive_test.gd b/game/tests/unit/core/battle_driver_drive_test.gd index 43ba5e85..706aa28e 100644 --- a/game/tests/unit/core/battle_driver_drive_test.gd +++ b/game/tests/unit/core/battle_driver_drive_test.gd @@ -55,6 +55,7 @@ func _init() -> void: # ① 缺省两参(真编排器):同种子两跑整轮驱动逐位(确定性自证——动作序列快照经 cell_revealed 事件流) func _smoke_drive_round_contract() -> void: +# oracle: design/engineering/architecture/architecture.md:144(效果端口件:收事件交结算取回命中批——驱动循环契约组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 Kit.check("前置:表加载零 error", load_ok) @@ -80,6 +81,7 @@ func _smoke_drive_round_contract() -> void: # ② provider 形态:策略回调产出 dig/seal_place/refresh 各一——提交与日志回调四元组在案 func _provider_shapes() -> void: +# oracle: design/engineering/architecture/architecture.md:144(端口应答束形状组) var st := _StubSettlement.new() var script := [ {"kind": "dig", "cell": Vector2i(1, 1)}, {"kind": "seal", "cell": Vector2i(2, 2)}, {"kind": "refresh"}, {"kind": "stop"} ] var logs: Array = [] @@ -100,6 +102,7 @@ func _provider_shapes() -> void: # ③ max_rejects:策略恒返被拒动作——第 3 拒后停手出环(循环次数=3) func _max_rejects() -> void: +# oracle: design/engineering/architecture/architecture.md:144(拒绝通道组) var st := _StubSettlement.new() st.minesweeper.accept = false st.minesweeper.reject_cells = {Vector2i(9, 9): true} @@ -119,6 +122,7 @@ func _max_rejects() -> void: # ④ 合步 next:move 后相位变更即断弃 dig(桩 on_submit 钩子把 phase 推离 Active) func _combined_step_next() -> void: +# oracle: design/engineering/architecture/architecture.md:144(合并步进组) var st := _StubSettlement.new() st.minesweeper.on_submit = func(action: String) -> void: if action == "move": diff --git a/game/tests/unit/core/board_gen_reward_test.gd b/game/tests/unit/core/board_gen_reward_test.gd index e7dd0c4f..3f465f05 100644 --- a/game/tests/unit/core/board_gen_reward_test.gd +++ b/game/tests/unit/core/board_gen_reward_test.gd @@ -18,6 +18,7 @@ var _rng func _init() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成宿主(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -51,6 +52,7 @@ func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator: # 公式 N×cell ≤ 可用高−12 必须计入 (N−1)×2px 网格间距,否则网格实高溢出被 clip_contents 裁掉; # 现公式 = 计入间距 + 垂直预留 24px〔上 12/下 12——底部不贴 D 区边框〕)---- func _smoke_layout_cell_formula() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75(出生区边长表单点——格边长公式组) print("--- 棋盘布局格边长公式(底部预留回归·四档板型)---") var board_generator = load("res://core/board_generator.gd").new() var board_view = load("res://presentation/views/board_view.gd").new() @@ -80,6 +82,7 @@ func _smoke_layout_cell_formula() -> void: # ---- 地图战斗资源·身份面单元冒烟(格面资源:roll 恰一次 / 雷安同池 / 幂等 / 清图批尾)---- func _smoke_reward_identity_unit() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(格面资源身份——三分出口与资源身份组) print("--- 地图战斗资源·格面资源身份面 ---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260911) @@ -141,6 +144,7 @@ class _BoardRowStub: func _smoke_board_generator() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221(确定性复现+布雷纪律组) print("--- 棋盘生成 ---") var board_generator = load("res://core/board_generator.gd").new() for L in [1, 2]: @@ -235,6 +239,7 @@ func _board_dump(b) -> String: # ---- 地图战斗资源·伤害量值区间冒烟(roll 域内 + 中点锚 + 变异实际流动 + 定值行不受扰)---- func _smoke_power_interval() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(资源量值区间——伤害量值区间组) print("--- 地图战斗资源·伤害量值区间(上阶/下阶随机)---") # ① 表侧静态锚(配置推导——改表不断锚):凡带 power_min/power_max 的行 = 区间行, # 校验配对齐全(lo ≤ hi)+ power = floor 中点(行动经济 条款①期望当量锚);其余行必须零区间字段。 diff --git a/game/tests/unit/core/board_generator_test.gd b/game/tests/unit/core/board_generator_test.gd index 4acd338d..182794f0 100644 --- a/game/tests/unit/core/board_generator_test.gd +++ b/game/tests/unit/core/board_generator_test.gd @@ -13,6 +13,7 @@ var _db: ConfigDb func _init() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:219 棋盘生成(装载组) _db = load("res://core/config_db.gd").new() as ConfigDb if not _db.load_tables("res://data/tables", false): Kit.check("数值表加载", false) @@ -39,6 +40,7 @@ func _generate(L: int, D: int, seed0: int) -> BoardData: func _test_generate_shape_and_mines() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224(布雷纪律组) var b := _generate(1, 0, 71000) if b == null: Kit.check("生成返回棋盘", false) @@ -59,6 +61,7 @@ func _test_generate_shape_and_mines() -> void: # 目标雷数公式单点(d14):同参同值对拍原式 + 真实产图指标读数同源 func _test_target_mine_count_single_source() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224(雷数单源组) var bad := 0 var checked := 0 for L in [1, 2, 3]: @@ -89,6 +92,7 @@ func _test_target_mine_count_single_source() -> void: func _test_digits_equal_neighbor_mine_count() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:223(数字恒真组) var b := _generate(1, 1, 72000) if b == null: Kit.check("生成返回棋盘(数字段)", false) @@ -109,6 +113,7 @@ func _test_digits_equal_neighbor_mine_count() -> void: func _test_same_seed_bit_identical() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221(同种子逐位复现组) var a := _generate(2, 0, 73000) var b := _generate(2, 0, 73000) if a == null or b == null: @@ -119,6 +124,7 @@ func _test_same_seed_bit_identical() -> void: func _test_clearable_and_inferability_selfconsistent() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:226(可清图与及格线自洽组) var b := _generate(1, 0, 74000) if b == null: Kit.check("生成返回棋盘(可推理段)", false) @@ -134,6 +140,7 @@ func _test_clearable_and_inferability_selfconsistent() -> void: # ---- V8 出生区边长表单点(架构整改——spawn_edge 表头驱动布雷域基/几何/护栏全导出)---- func _test_spawn_edge_from_table() -> void: +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75(出生区边长读表组) # ① 纯函数对拍:edge=5 夹具 → target = round(d×(W×H−25))(25 格布雷域基可观测) var p5 := {"w": 12, "h": 12, "density": 0.15, "spawn_edge": 5} Kit.check("V8 纯函数:edge=5 布雷域基 25 格(target=round(d×(144−25)))〔架构整改 V8〕", diff --git a/game/tests/unit/core/breakpoint_resume_test.gd b/game/tests/unit/core/breakpoint_resume_test.gd index 9de8b519..c68200b9 100644 --- a/game/tests/unit/core/breakpoint_resume_test.gd +++ b/game/tests/unit/core/breakpoint_resume_test.gd @@ -15,6 +15,7 @@ var _bp_path := "user://test_breakpoint_resume.json" func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(断点域契约:七字段往返/续玩确定性/幂等/三结局清除组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[breakpoint_resume_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/config_db_cross_validator_test.gd b/game/tests/unit/core/config_db_cross_validator_test.gd index 8ad71acb..0b82f07d 100644 --- a/game/tests/unit/core/config_db_cross_validator_test.gd +++ b/game/tests/unit/core/config_db_cross_validator_test.gd @@ -13,6 +13,7 @@ const StatusTableScript := preload("res://data/schemas/status_effect_table.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(跨表校验宿主——装载组) _test_all_links_valid() _test_dangling_skill_id() _test_cast_side_mismatch() @@ -46,6 +47,7 @@ func _cats(errs: Array) -> Array[String]: # ---- 正向:技能池全对齿(普攻恰一行、buff 挂敌方侧)应零错,防校验误伤 ---- func _test_all_links_valid() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(悬空引用类别:跨表链接全有效组) var skills := _skill([ {"skill_id": "attack", "type": "basic"}, {"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_strong"}, @@ -61,6 +63,7 @@ func _test_all_links_valid() -> void: # ---- 负向:技能池引用表里不存在的 skill_id 必须报 ---- func _test_dangling_skill_id() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(悬空引用类别:skill_id 悬空组) var skills := _skill([{"skill_id": "attack", "type": "basic"}]) var enemies := _enemy([ {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [ @@ -73,6 +76,7 @@ func _test_dangling_skill_id() -> void: # ---- 负向:buff 挂到主角侧状态(side 反向)必须报 ---- func _test_cast_side_mismatch() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(side 反向校验组) var skills := _skill([{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_hero_side"}]) var enemies := _enemy([ {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [ diff --git a/game/tests/unit/core/config_db_resolver_test.gd b/game/tests/unit/core/config_db_resolver_test.gd index a28f2472..40626662 100644 --- a/game/tests/unit/core/config_db_resolver_test.gd +++ b/game/tests/unit/core/config_db_resolver_test.gd @@ -12,6 +12,7 @@ var _resolver: ConfigDbResolver func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(解析器宿主——装载组) _resolver = ResolverScript.new( func(level, category, message) -> void: # 诊断通道签名按调用实参收(首参为表 id 字符串) _diags.append("%s/%s/%s" % [str(level), str(category), str(message)]), @@ -54,6 +55,7 @@ func _difficulty_table(): func _test_band_hit() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(精确命中零警告组) 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", "")) == "giant_spider") @@ -63,6 +65,7 @@ func _test_band_hit() -> void: func _test_rowset_order_preserved() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(行集寻址与行序保序组) var rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle") var ids: Array[String] = [] for r in rows: @@ -72,6 +75,7 @@ func _test_rowset_order_preserved() -> void: func _test_top_band_clamp() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(超最大行走封顶取最大行组) _diags.clear() var rows: Array = _resolver.resolve_enemy_rowset(1, 99, "battle") Kit.check("越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕", @@ -87,6 +91,7 @@ func _test_top_band_clamp() -> void: func _test_row_kind_routing() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(行类别路由组) var battle_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle") var boss_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "boss") Kit.check("行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕", @@ -95,6 +100,7 @@ func _test_row_kind_routing() -> void: func _test_request_oob() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:203(越域致命中止组) _diags.clear() _resolver.resolve_enemy_rowset(0, 0, "battle") Kit.check("关卡序号 <1 → fatal request_oob〔fv5〕", _has_fatal_oob()) @@ -114,6 +120,7 @@ func _has_fatal_oob() -> bool: func _test_difficulty_clamp() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(难度 clamp 组) var m: Dictionary = _resolver.resolve_difficulty_modifiers(1, 0) Kit.check("难度乘数越域被 clamp:hp 99 → 上限 10", absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001) Kit.check("难度乘数越域被 clamp:atk 0.01 → 下限 0.1", absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001) diff --git a/game/tests/unit/core/curio_refresh_paid_test.gd b/game/tests/unit/core/curio_refresh_paid_test.gd index ca6fb88e..94e2a06f 100644 --- a/game/tests/unit/core/curio_refresh_paid_test.gd +++ b/game/tests/unit/core/curio_refresh_paid_test.gd @@ -8,6 +8,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/engineering/architecture/architecture.md:107(CurioDeck:三选一/商店共享池、抽签、cap 库存——刷新付费组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[curio_refresh_paid_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/effect_words_t1_test.gd b/game/tests/unit/core/effect_words_t1_test.gd index 0b1a196b..265ff860 100644 --- a/game/tests/unit/core/effect_words_t1_test.gd +++ b/game/tests/unit/core/effect_words_t1_test.gd @@ -11,6 +11,7 @@ const EffectEngineScript = preload("res://core/effect_engine.gd") func _init() -> void: +# oracle: design/engineering/architecture/architecture.md:89(效果词表封闭——扩签封闭性组) # ① 词表在册断言(扩签面:2 事件+8 事实+7 效果类型+1 算子) Kit.check_quiet(EffectEngineScript.EVENTS.has("dig_stop") and EffectEngineScript.EVENTS.has("battle_won"), "新事件在册:dig_stop/battle_won〔专长批 V1〕") diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd b/game/tests/unit/core/encapsulation_p2_sweep_test.gd index 5bfa81e5..3e55fad3 100644 --- a/game/tests/unit/core/encapsulation_p2_sweep_test.gd +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd @@ -11,29 +11,34 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/engineering/code-conventions.md:41(封装边界:跨对象往来只走公开方法与属性——P2 词表单点扫描组) _sweep_generator() _sweep_production_face() quit(Kit.finish("encapsulation_p2_sweep_test")) func _src(path: String) -> String: +# oracle: design/engineering/code-conventions.md:41(源码采样组) 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: +# oracle: design/engineering/code-conventions.md:41(零命中扫描组) 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: +# oracle: design/engineering/code-conventions.md:41(正锚存在组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 正锚在案〔架构P2〕" % [label, pattern], re.search(src) != null) # ① V1 生成器词表定义删除与改指 func _sweep_generator() -> void: +# oracle: design/engineering/code-conventions.md:41(生成器本地副本归零组) var src := _src("res://core/node_map_generator.gd") if src.length() == 0: return @@ -43,6 +48,7 @@ func _sweep_generator() -> void: # ② V2 生产三目录定义唯一性(递归扫 .gd,恰好 1 处且落 level_table.gd) func _sweep_production_face() -> void: +# oracle: design/engineering/code-conventions.md:41(schema 唯一定点组) var files: Array[String] = [] for dir_path in ["res://core", "res://data", "res://presentation"]: _collect_gd(dir_path, files) diff --git a/game/tests/unit/core/encapsulation_sweep_test.gd b/game/tests/unit/core/encapsulation_sweep_test.gd index 56acd449..06af956c 100644 --- a/game/tests/unit/core/encapsulation_sweep_test.gd +++ b/game/tests/unit/core/encapsulation_sweep_test.gd @@ -14,6 +14,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/engineering/code-conventions.md:41(封装边界——五处结构性零命中扫描组) _sweep_settlement_segments() _sweep_orchestrator_methods() _sweep_breakpoint_pair() @@ -25,6 +26,7 @@ func _init() -> void: # 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) func _src(path: String) -> String: +# oracle: design/engineering/code-conventions.md:41(源码采样组) var src := FileAccess.get_file_as_string(path) Kit.check("扫描面非空前置:%s" % path, src.length() > 0) return src @@ -32,18 +34,21 @@ func _src(path: String) -> String: # 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位) func _zero(pattern: String, src: String, label: String, path: String) -> void: +# oracle: design/engineering/code-conventions.md:41(零命中扫描组) 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: +# oracle: design/engineering/code-conventions.md:41(正锚存在组) 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: +# oracle: design/engineering/code-conventions.md:41(结算段封装扫描组) var classes := { "res://core/settlement/grant_segment.gd": "GrantSegment", "res://core/settlement/combat_segment.gd": "CombatSegment", @@ -63,6 +68,7 @@ func _sweep_settlement_segments() -> void: # ② V3 编排器搬家五方法零残留 func _sweep_orchestrator_methods() -> void: +# oracle: design/engineering/code-conventions.md:41(编排器方法面扫描组) var src := _src("res://core/battle_orchestrator.gd") if src.length() == 0: return @@ -72,6 +78,7 @@ func _sweep_orchestrator_methods() -> void: # ③ V4 断点域(adventure_runner → BreakpointStore 公开面) func _sweep_breakpoint_pair() -> void: +# oracle: design/engineering/code-conventions.md:41(断点对封装扫描组) var src := _src("res://core/adventure_runner.gd") if src.length() == 0: return @@ -81,6 +88,7 @@ func _sweep_breakpoint_pair() -> void: # ④ V5 存档双向公开面(save_manager↔slot_store) func _sweep_save_pair() -> void: +# oracle: design/engineering/code-conventions.md:41(存档对封装扫描组) 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") @@ -94,6 +102,7 @@ func _sweep_save_pair() -> void: # ⑤ V6 效果宿主累积面(grant → BattleFxHost 公开累积方法) func _sweep_fx_host_accumulators() -> void: +# oracle: design/engineering/code-conventions.md:41(特效宿主累积面扫描组) var src := _src("res://core/settlement/grant_segment.gd") if src.length() == 0: return @@ -103,6 +112,7 @@ func _sweep_fx_host_accumulators() -> void: # ⑥ V7 配置挂载面(config_db → 三 schema bind_resolver) func _sweep_config_resolver() -> void: +# oracle: design/engineering/code-conventions.md:41(配置挂载面扫描组) var src := _src("res://core/config_db.gd") if src.length() == 0: return diff --git a/game/tests/unit/core/feat_vouchers_test.gd b/game/tests/unit/core/feat_vouchers_test.gd index 4e4cc4ac..cda7db0d 100644 --- a/game/tests/unit/core/feat_vouchers_test.gd +++ b/game/tests/unit/core/feat_vouchers_test.gd @@ -16,6 +16,7 @@ func EffectEngineKindProbe(_db) -> bool: func _init() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(奇物凭证五件与留任二件组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[feat_vouchers_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/hero_ledger_test.gd b/game/tests/unit/core/hero_ledger_test.gd index 4102292b..e8e30b4c 100644 --- a/game/tests/unit/core/hero_ledger_test.gd +++ b/game/tests/unit/core/hero_ledger_test.gd @@ -14,6 +14,7 @@ const LevelRunnerScript := preload("res://core/level_runner.gd") func _init() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:288(生命一致性:三写入口封顶 [0,生效上限]——主角账组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[hero_ledger_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/level_map_type_vis_test.gd b/game/tests/unit/core/level_map_type_vis_test.gd index d1bade78..f97fdd19 100644 --- a/game/tests/unit/core/level_map_type_vis_test.gd +++ b/game/tests/unit/core/level_map_type_vis_test.gd @@ -10,6 +10,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd") func _init() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(信息迷雾类型可见性宿主——装载组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "可见性用例:数值表加载成功") @@ -102,6 +103,7 @@ func _runner(db, nodes: Array, anchor_id: int, candidates: Array, n: int = 2): # ---- ⑥ N 进数据表(含越域校验)〔fv6〕---- func _test_field_in_table(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(迷雾参数读表——字段在表组) var n_in_table: int = int(db.run.level.info_visible_layers) Kit.check("N 落表:level.tres 读数 2(实际 %d)〔fv6〕" % n_in_table, n_in_table == 2) var t := LevelTableScript.new() @@ -125,6 +127,7 @@ func _has_n_domain_err(errs: Array) -> bool: # ---- ① 邻近 N 层见类型(层距边界)〔fv1〕---- func _test_layer_distance(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(层距 N 可见性组) var nodes := _graph_nodes() var r = _runner(db, nodes, 1, [nodes[2]]) # 锚 = 节点 1(层 1),候选 = 节点 2 var vis: Array = r.type_visible_ids() @@ -136,6 +139,7 @@ func _test_layer_distance(db) -> void: # ---- ② 下一跳恒可见(跨层跳边候选)〔fv4〕---- func _test_candidate_exempt(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(候选豁免组) var nodes := _graph_nodes() var r = _runner(db, nodes, 6, [nodes[3]]) # 锚 = 节点 6(层 5),候选 = 节点 3(层 2,层距 3) var vis: Array = r.type_visible_ids() @@ -146,6 +150,7 @@ func _test_candidate_exempt(db) -> void: # ---- ③ Boss/终点不入雾+⑤ 入口停点锚层 0〔fv3〕〔fv1〕---- func _test_boss_exempt(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(Boss 豁免组) var nodes := _graph_nodes() var r = _runner(db, nodes, -1, [nodes[0]]) # 入口停点(无当前节点,锚层 0) var vis: Array = r.type_visible_ids() @@ -158,6 +163,7 @@ func _test_boss_exempt(db) -> void: # ---- ④ 单圈回雾(visited 不豁免)〔fv7〕---- func _test_single_circle(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(单圈可见组) var nodes := _graph_nodes() var r = _runner(db, nodes, 6, [nodes[7], nodes[3]]) r.visited.assign([nodes[0], nodes[1], nodes[2], nodes[3], nodes[4], nodes[5], nodes[6]]) @@ -170,6 +176,7 @@ func _test_single_circle(db) -> void: # ---- ⑤ 单链退化形态(出度 ≤1 图,编号 = id 句柄)〔fv1〕---- func _test_single_chain_entry(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(单链入口组) var nodes := _chain_nodes() var r = _runner(db, nodes, -1, [nodes[0]]) var vis: Array = r.type_visible_ids() @@ -182,6 +189,7 @@ func _test_single_chain_entry(db) -> void: # ---- ⑥ N 读表:可见窗随表值随动〔fv6〕---- func _test_n_from_table(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(N 读表组) var nodes := _graph_nodes() var r = _runner(db, nodes, 1, [nodes[2]]) # 锚层 1 db.run.level.info_visible_layers = 1 diff --git a/game/tests/unit/core/level_shop_economy_test.gd b/game/tests/unit/core/level_shop_economy_test.gd index 5ddd10a9..7fafb4c9 100644 --- a/game/tests/unit/core/level_shop_economy_test.gd +++ b/game/tests/unit/core/level_shop_economy_test.gd @@ -261,6 +261,7 @@ func _init() -> void: # ---- 数值入表等价证明(d11/d16a——原字面量迁移:表值 == 迁移前字面量,消费端读表)---- func _smoke_table_migration() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(行集纪律——表迁移组) print("--- 数值入表(shop 两 knob + hero 显示名 · 原值迁移)---") Kit.check("shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕", _db.run.shop.relic_base_price == 15.0) Kit.check("hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕", _db.combat.hero.display_name == "探险者") @@ -275,6 +276,7 @@ func _smoke_table_migration() -> void: # 敌侧/板侧解析一律走 ConfigDB 真实消费链,禁直读行集自算)---- func _smoke_generated_coverage() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(每终点前恰一店——生成覆盖扫描组) var enemy_ok := true var board_ok := true var boss_band_ok := true @@ -325,6 +327,7 @@ func _smoke_generated_coverage() -> void: # ---- 关卡节点图·生成图推进冒烟(图 = 该关参数档 × 种子)---- func _smoke_level() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(关卡图校验推进组) print("--- 关卡节点图·生成图推进(参数档×种子产图·满配冒险成功/恢复公式/败北线/回放确定性)---") var autoplay = load("res://sim/level_autoplay.gd") var hero_base := {"atk": int(_db.combat.hero.atk), "hp": int(_db.combat.hero.max_hp), "ac": int(_db.combat.hero.armor_class)} # 配置基线快照——⑤ 回基线对比用(改表自动跟随,不再硬编码) @@ -438,6 +441,7 @@ func _drive_to_rest(level_index: int, seed_val: int, gap: int) -> Dictionary: # ---- 分支节点图冒烟:图合法性校验 + L2 幽暗地域生成图行进(图 = 参数档 × 种子)---- func _smoke_level_graph() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(图结构与行进纪律组) print("--- 关卡节点图·分支节点图(L2 幽暗地域·生成图)---") var autoplay = load("res://sim/level_autoplay.gd") # ① 关卡表行级校验零错误(关卡表只承载序号/显示名/恢复比例——图合法性由 validate_graph 在起关时承担) @@ -795,6 +799,7 @@ func _fresh_rng(seed_val: int): # ---- ① GP 冒险钱包·到账 ---- func _smoke_gp_wallet() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(GP 钱包入账组) print("--- GP 冒险钱包·到账(战斗完成入账恰一次 + Boss 加值)---") var autoplay = load("res://sim/level_autoplay.gd") # ① L1 首战(普通战档——掷定 [2,4] + 关级加值 0):runner 驱动,收集 battle_finished 与钱包事件 @@ -860,6 +865,7 @@ func _escrow_sum_of(rows: Array) -> int: # ---- ② escrow 隔离(GP安全格兑付 / 破财直验 / 钱包 clamp)---- func _smoke_gp_escrow() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(GP escrow 组) print("--- GP escrow 隔离(GP安全格兑付入缓冲 · 破财不透支)---") # ① 种子扫描:找GP 袋(gp 型安全格)可挖掘局——挖开兑付 +3 入 escrow,战斗末随 gp_dropped 一次上抛 var scan := {} @@ -973,6 +979,7 @@ func _find_gp_dig(minesweeper, settlement) -> Dictionary: # ---- ③ 商店房三收口 ---- func _smoke_shop_room() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(商店房呈现组) print("--- 商店房三收口(现价/加算区/余额不足拒/离开回选路/收束清零)---") var autoplay = load("res://sim/level_autoplay.gd") # 行进到商店房(按图型别定位商店节点——候选取最小句柄行进,到达即停): @@ -1106,6 +1113,7 @@ func _shop_products_by_id(prods: Array) -> Dictionary: # ---- ④ 商店编排性质(生成图扫描面——三条性质 + 终点跨表解析)---- func _smoke_shop_graph() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(必经恰一店图纪律组) print("--- 商店编排性质(每终点前一位恰一店 · 店数=终点数 · 全路径恰穿一店)---") var pred_bad := 0 var term_total := 0 @@ -1176,6 +1184,7 @@ func _smoke_shop_graph() -> void: # ---- ⑥ 大岔口分区域兑付(V3——多终点图走哪条路决定打谁,任一 Boss 胜即收束恰一次)---- func _smoke_multi_terminal_settlement() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:185(多终局收束组) print("--- 大岔口分区域兑付(L2 两轴各打各的终点)---") var autoplay = load("res://sim/level_autoplay.gd") var nodes: Array = _generated_graph(2, 42).get("nodes", []) @@ -1243,6 +1252,7 @@ func _paths_all_one_shop(nodes: Array) -> bool: # ---- ⑤ 商店共享池(三选一同构/乘法价/跨渠道共账/切边/断点扩键)---- func _smoke_shop_pool() -> void: +# oracle: design/engineering/architecture/architecture.md:107(商店共享池组) print("--- 商店共享池(抽3买1全重抽 · 乘法定价 ceil · 池空硬闸 · 切边零入店)---") var autoplay = load("res://sim/level_autoplay.gd") var shelf_kinds: Array = [] @@ -1405,6 +1415,7 @@ func _smoke_shop_pool() -> void: # ---- 商店房 node_entered 载荷(尾波 V1——_enter_node pool/legacy 双发射路径归一后的行为保持断言)---- func _test_shop_node_entered_payload() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(行进至此打开商店覆盖层——载荷组) print("--- 商店房 node_entered 载荷(pool 含 shelf:true+gp 现值/legacy 无 shelf 键)---") var autoplay = load("res://sim/level_autoplay.gd") # pool 态:载荷字典经信号闭包当场取样(字典为引用累积器——lambda 按值捕获标量的绕行惯例) @@ -1563,6 +1574,7 @@ func _has_err_category(errs: Array, category: String) -> bool: # ---- 货架位数读表(架构整改——shelf_size 表头单点:进店与重抽同源)---- func _test_shelf_size_from_table() -> void: +# oracle: design/gdd/ux/shop.md:13(货架位数读表组) var autoplay = load("res://sim/level_autoplay.gd") var st = _db.run.shop var saved: int = int(st.shelf_size) @@ -1584,6 +1596,7 @@ func _test_shelf_size_from_table() -> void: # ---- 散折价收回规则键(架构整改 R2——shop.spend_discount 表字段退役,折价读 economy_spend.discount_pct)---- func _test_spend_discount_from_rule_key() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(换图基座:挖格产 RAP 与刷新折扣组) # 字段真删:shop.tres 加载零诊断 + spend_discount 属性不在位 Kit.check("shop.tres 加载零诊断(spend_discount 字段真删——schema 与表行同卸)〔架构整改 V2〕", _db.diagnostics_count("error") == 0 and _db.run.shop.get("spend_discount") == null) diff --git a/game/tests/unit/core/level_table_validate_test.gd b/game/tests/unit/core/level_table_validate_test.gd index 437cdfee..b845d220 100644 --- a/game/tests/unit/core/level_table_validate_test.gd +++ b/game/tests/unit/core/level_table_validate_test.gd @@ -11,6 +11,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd") func _init() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(关卡表消费侧校验宿主) _test_positive() _test_negative() _test_scope_not_extended() @@ -37,6 +38,7 @@ func _has(errs: Array, cat: String, needle: String = "") -> bool: # ---- 正例(四检全过)---- func _test_positive() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(良构基底正向组) # 多入口多终点:两入口共汇入一岔口,两终点各在互异深度 var multi := [ _n(0, "battle", 0, [2]), _n(1, "battle", 0, [2]), @@ -59,6 +61,7 @@ func _test_positive() -> void: # ---- 负例九类(各报对应类别与判据消息)---- func _test_negative() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(违规配置加载期致命——负向组) var non_boss_end := [_n(0, "battle", 0, [1]), _n(1, "battle", 1, [])] Kit.check("负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕", _has(_errs(non_boss_end), "structural", "终点 1 非 Boss")) @@ -92,6 +95,7 @@ func _test_negative() -> void: # ---- 「勿扩勿缩」反证:本检不含商店位与个数对参数档的核对 ---- func _test_scope_not_extended() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(校验范围不外扩组) # 合法形状但全程无商店/三个终点同入口图——四检不该因此报错(商店位归冒烟断言、个数归产出侧) var no_shop := [_n(0, "battle", 0, [1, 2]), _n(1, "battle", 1, [3]), _n(2, "rest", 1, [3]), _n(3, "boss", 2, [])] Kit.check("范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕", _errs(no_shop).is_empty()) diff --git a/game/tests/unit/core/meta_progression_forge_test.gd b/game/tests/unit/core/meta_progression_forge_test.gd index 84bdd583..1e4af588 100644 --- a/game/tests/unit/core/meta_progression_forge_test.gd +++ b/game/tests/unit/core/meta_progression_forge_test.gd @@ -21,6 +21,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/跨局循环/元进度.md:148 元进度与锻造宿主(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -132,6 +133,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void: # ---- 元进度 ②树购买面(购买受理/持久/余额不足拒绝/未知节点拒绝; # 行价=表值 flat 单价)---- func _meta_pricing_and_buy() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:198(单币可判:购买只扣钥石、余额不足拒绝零变化组) print("--- 元进度·树购买面(隔离档 smoke_meta_buy/dry)---") _meta_smoke_cleanup() var sv = _meta_new_save(_META_SMOKE_PATHS[0]) @@ -158,6 +160,7 @@ func _meta_pricing_and_buy() -> void: # ---- 元进度 ⑥Boss 两档 ⑦钥石不丢 ⑧工坊解锁 ⑩摘要副本与 cap(合成摘要驱动——settle 纯账面零跑局)---- func _meta_settle_and_workshop() -> void: +# oracle: design/gdd/systems/跨局循环/元进度.md:150(钥石收束入账不丢组) print("--- 元进度·收束入账与工坊(隔离档 smoke_meta_settle/workshop/cap)---") var sv = _meta_new_save(_META_SMOKE_PATHS[2]) var mp = load("res://core/meta_progression.gd").new(_db, sv) @@ -350,6 +353,7 @@ func _meta_settle_and_workshop() -> void: # ---- 元进度 ④注入可见 + 白板成立(验收 4/2/3——程序面;MC 逐位回归归 T7 全量验证)---- func _meta_injection() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:201(注入可判:开局覆盖值分层可加组) print("--- 元进度·注入可见与白板(隔离档 smoke_meta_white/dry)---") var sv = _meta_new_save(_META_SMOKE_PATHS[1]) var mp = load("res://core/meta_progression.gd").new(_db, sv) @@ -393,6 +397,7 @@ func _meta_injection() -> void: # ---- 锻造工坊·两根类型轴所有权移交(V5 前半:hero.tres 两轴删除+锻造配置默认值)---- # 后半(build_adventure_params 注入默认值/选中覆盖)随 T4 注入接线同批断言;写口/解锁面归 T3。 func _smoke_forge_axes() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220(锻造两轴单选注入与树零攻防组) print("--- 锻造工坊·类型轴所有权移交(T2)---") # V5:hero.tres 静态表两轴已删(全表加载零违例——config_db_test 同款闸;此处钉字段不在位) Kit.check("hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕", @@ -555,6 +560,7 @@ func _smoke_forge_axes() -> void: # ---- 天赋树冒烟(v0.8 12 节点:schema/购买面/Fork 一节点双态/彩蛋特化)---- func _smoke_tree() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:194(天赋树购买与六键合成组) print("--- 天赋树(12 节点 · 购买面 · Fork 双态 · 彩蛋觉醒)---") # 〔V1〕结构:12 行 + schema 零违例 Kit.check("meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕", @@ -637,6 +643,7 @@ func _smoke_tree() -> void: # ---- 天赋树六键行为冒烟(V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿; # 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))---- func _smoke_tree_rules() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:194(规则键参数读表组) print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---") # 开局姿态组:每关首轮窗(battle_index 事实——引擎直构 + 自建 ctx) var fx = load("res://core/effect_engine.gd").new(null) @@ -777,6 +784,7 @@ func _smoke_tree_rules() -> void: # ---- 锻造契约补齐(断言补齐批——⑥锻造工坊):拒购零副作用/祝福件零泄漏/默认值解耦/甲型双轴 ---- func _test_forge_buy_reject() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:222(钥石不足购买被拒、余额与等级零变化组) var sv = _meta_new_save("user://smoke_meta_reject.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) sv.meta_state._add_keystone(10) # 低于首购价 20——余额不足面 @@ -791,6 +799,7 @@ func _test_forge_buy_reject() -> void: func _test_blessing_no_forge_leak() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220(树淬炼对攻击与 AC 零贡献组) var sv = _meta_new_save("user://smoke_meta_blessing.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) var fs0: Dictionary = sv.meta_state.forge_state().duplicate(true) @@ -809,6 +818,7 @@ func _test_blessing_no_forge_leak() -> void: func _test_default_axes_pre_key() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:217(未点亮期间默认值照常注入组) var sv = _meta_new_save("user://smoke_meta_axesw.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) var ax: Dictionary = mp.hero_type_axes() @@ -817,6 +827,7 @@ func _test_default_axes_pre_key() -> void: func _test_armor_switch_dual_axes() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:221(换装不丢级不生效——双轴组) var sv = _meta_new_save("user://smoke_meta_axesw.json") sv.meta_state._add_keystone(1000) var mp = load("res://core/meta_progression.gd").new(_db, sv) diff --git a/game/tests/unit/core/minesweeper_rules_test.gd b/game/tests/unit/core/minesweeper_rules_test.gd index 5f4bdcc0..cb3b50ba 100644 --- a/game/tests/unit/core/minesweeper_rules_test.gd +++ b/game/tests/unit/core/minesweeper_rules_test.gd @@ -17,6 +17,7 @@ var _ae: ActionEconomy func _init() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:224(排雷规则宿主——装载组) _db = load("res://core/config_db.gd").new() as ConfigDb if not _db.load_tables("res://data/tables", false): Kit.check("数值表加载", false) @@ -45,6 +46,7 @@ func _init() -> void: func _test_install_invariants() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:228(装板四不变式组) _rules.install_board(_board) Kit.check("装板后 current_board 即所装棋盘", _rules.current_board() == _board) var opened: int = _rules.revealed_cells().size() @@ -68,6 +70,7 @@ func _unrevealed_safe() -> Vector2i: func _test_dig_face_rules() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(挖/封印合法性与可辨拒绝组) var revealed_first = _rules.revealed_cells()[0] as Vector2i var out: Array = _rules.settle_batch([{"action": "dig", "cell": revealed_first}]) var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {} @@ -82,6 +85,7 @@ func _test_dig_face_rules() -> void: func _test_reveal_advances() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230(揭示推进与连锁组) var c := _unrevealed_safe() if c == Vector2i(-1, -1): Kit.check("存在未揭示安全格(揭示推进前置)", false) @@ -95,6 +99,7 @@ func _test_reveal_advances() -> void: func _test_seal_place() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(重复封印拒绝组) var c := _unrevealed_safe() if c == Vector2i(-1, -1): Kit.check("存在未揭示安全格(封印前置)", false) @@ -107,6 +112,7 @@ func _test_seal_place() -> void: func _test_reject_out_of_catalog() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236(目录外拒绝组) var out: Array = _rules.settle_batch([{"action": "not_a_real_action", "cell": Vector2i(0, 0)}]) var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {} Kit.check("目录外意图被拒(不静默吞掉)——实际载荷 %s" % str(o).substr(0, 80), @@ -116,6 +122,7 @@ func _test_reject_out_of_catalog() -> void: # ---- V14 棋盘揭示预览只读口(架构整改——preview_reveal 与实挖揭示集同口径、零状态写入零事件)---- func _test_preview_reveal_readonly() -> void: +# oracle: design/engineering/architecture/architecture.md:103(preview_reveal 只读预览口——零写入零事件组) var events := 0 _rules.cell_revealed.connect(func(_evt): events += 1) # 零发射哨兵(preview 全程不触发揭示事件) # 形态 A:封印在场——preview(未开格) 不越封印且零状态写入 diff --git a/game/tests/unit/core/node_map_generator_test.gd b/game/tests/unit/core/node_map_generator_test.gd index 1c1e8acb..c57b11a9 100644 --- a/game/tests/unit/core/node_map_generator_test.gd +++ b/game/tests/unit/core/node_map_generator_test.gd @@ -77,6 +77,7 @@ func _max_depth(g: Dictionary) -> int: # ---- V1 产出合格:三档×种子扫描,自证零错误 ---- func _test_output_ok() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(产出约束六条+结构铁律组) var all_ok := true var entry_exit_ok := true for L in [1, 2, 3]: @@ -94,6 +95,7 @@ func _test_output_ok() -> void: # ---- V3 可复现:纯函数+counter 无关 ---- func _test_reproducible() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(同种子同参数同图组) var same := true for L in [1, 2, 3]: if _sig(_graph(L, 42)) != _sig(_graph(L, 42)): @@ -124,6 +126,7 @@ func _test_reproducible() -> void: # ---- V5 岔路取舍两条 ---- func _test_fork_rules() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:88(岔路取舍组) var next_hop_ok := true var comp_ok := true for L in [1, 2, 3]: @@ -199,6 +202,7 @@ func _branch_types(g: Dictionary, head_id: int, sibling_outs: Array) -> Array: # ---- V6 必经恰一店+归属锁定+多终点深度互异 ---- func _test_shop_and_attribution() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(必经恰一店与归属锁定组) var paths_ok := true var lock_ok := true var depth_ok := true @@ -251,6 +255,7 @@ func _all_paths(g: Dictionary) -> Array: # ---- V7 战斗保底 ---- func _test_battle_floor() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(战斗保底组) var ok := true for L in [1, 2, 3]: for s in range(42, 52): @@ -262,6 +267,7 @@ func _test_battle_floor() -> void: # ---- V9 含岔口与出口结构 ---- func _test_exit_structure() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(出口结构与分支互斥组) var fork_all := true var l1_single := true var big_fork_ok := true @@ -308,6 +314,7 @@ func _terminals_reachable(g: Dictionary, id: int) -> int: # ---- V9 Boss 出场门控(难度曲线与Boss——每区域恰一 Boss 终点)---- func _test_boss_exit_per_region() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(每区域 Boss 出口组) # 真实生成多区域图:全部终点 type=="boss"、boss 终点数==exit_count、深度=L−r(r∈[0,exit_count) # ——每区域恰一 boss、深度互异即分属不同区域) # 节点 depth 字段=入口起最长路径深(生成器校验轴);区域序号 r 的可观测形态=最深 boss(r=0) @@ -343,6 +350,7 @@ func _test_boss_exit_per_region() -> void: # ---- V10 RNG 单点(纯流面 counter 不变)---- func _test_rng_single_point() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(RNG 单点流组) var svc = RngScript.new() svc.new_adventure(4242) var before: int = svc.counter() @@ -357,6 +365,7 @@ func _test_rng_single_point() -> void: # ---- V1 违规夹具:各报对应错误类别 ---- func _test_negative_fixtures() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198(无解 fail-fast 夹具组) # 夹具构造器:手写 nodes/entries/terminals var dup := _fx([ {"id": 0, "type": "battle", "depth": 0, "next": [1]}, @@ -483,6 +492,7 @@ func _has_msg(errs: Array, needle: String) -> bool: # ---- V6 节点图三参数表值驱动(架构整改——fork_third_way_pct/fill_rest_first_pct/width_max 入 level_gen_params)---- func _test_gen_params_table_driven() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(参数档读表驱动组) # ① 同种子真实表行产图逐位(表值驱动回归锚) var g1 := _graph(2, 42) var g2 := _graph(2, 42) diff --git a/game/tests/unit/core/node_map_growth_test.gd b/game/tests/unit/core/node_map_growth_test.gd index 3ab25ad9..864b8674 100644 --- a/game/tests/unit/core/node_map_growth_test.gd +++ b/game/tests/unit/core/node_map_growth_test.gd @@ -38,6 +38,7 @@ func _reading(gen, db, L: int) -> Dictionary: # ---- V8 生长递增(三档×多种子逐档读数)---- func _test_growth(gen, db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:130(生长递增组) var steps_ok := true var count_ok := true for s in [42, 43, 44, 45, 46]: @@ -61,6 +62,7 @@ func _test_growth(gen, db) -> void: # ---- V4 参数无解判定(静态面:域/预算无解夹具必须被 check_params 拦下)---- func _test_fail_fast_static(gen, db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198(参数无解静态拒绝组) Kit.check("生产三档参数档可行性判定零错误〔fv4〕", NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(1)).is_empty() and NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(3)).is_empty()) @@ -79,6 +81,7 @@ func _test_fail_fast_static(gen, db) -> void: # ---- V4 致命中止真实通道(子进程退出码非零;零回退分支=件核)---- func _test_fatal_subprocess() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(无解致命子进程组) var bin := _engine_bin() if bin == "": Kit.check("未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕", false) diff --git a/game/tests/unit/core/playtest_logger_guard_test.gd b/game/tests/unit/core/playtest_logger_guard_test.gd index 1f1ec9eb..670be3ab 100644 --- a/game/tests/unit/core/playtest_logger_guard_test.gd +++ b/game/tests/unit/core/playtest_logger_guard_test.gd @@ -13,6 +13,7 @@ func _init() -> void: func _smoke_required_injection() -> void: +# oracle: design/engineering/code-conventions.md:40(拆分交接:依赖构造期一次交付——日志器必填注入守卫组) # 未注入(缺省 0):start_session 返回空串、零文件产出 var lg0 = load("res://presentation/playtest_logger.gd").new() var p0: String = lg0.start_session(42) diff --git a/game/tests/unit/core/pool_case_d_test.gd b/game/tests/unit/core/pool_case_d_test.gd index 86aabf8d..814af348 100644 --- a/game/tests/unit/core/pool_case_d_test.gd +++ b/game/tests/unit/core/pool_case_d_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/engineering/architecture/architecture.md:107(池结构案 D:候选抽取与池纪律组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[pool_case_d_test] FAIL — 数值表加载失败") @@ -65,6 +66,7 @@ func _tiers_of(cands: Array) -> Array: func check_v7(a, db) -> void: +# oracle: design/engineering/architecture/architecture.md:107(池案 D V7 组) if not a.start_adventure(201): Kit.check_quiet(false, "V7 前置:冒险启动") return @@ -110,6 +112,7 @@ func check_v7(a, db) -> void: func check_v6(b, db) -> void: +# oracle: design/engineering/architecture/architecture.md:107(池案 D V6 组) if not b.start_adventure(202): Kit.check_quiet(false, "V6 前置:冒险启动") return @@ -139,6 +142,7 @@ func check_v6(b, db) -> void: func check_v12(m, db) -> void: +# oracle: design/engineering/architecture/architecture.md:107(池案 D V12 组) if not m.start_adventure(401): Kit.check_quiet(false, "V12 前置:冒险启动") return @@ -157,6 +161,7 @@ func check_v12(m, db) -> void: func check_v8(c, db) -> void: +# oracle: design/engineering/architecture/architecture.md:107(池案 D V8 组) if not c.start_adventure(203): Kit.check_quiet(false, "V8 前置:冒险启动") return diff --git a/game/tests/unit/core/run_time_fatal_test.gd b/game/tests/unit/core/run_time_fatal_test.gd index cd80a919..0178c303 100644 --- a/game/tests/unit/core/run_time_fatal_test.gd +++ b/game/tests/unit/core/run_time_fatal_test.gd @@ -20,6 +20,7 @@ const TAG_OK := "board_gen" # 在册合法标签(PURPOSE_TAGS 含;wr7 必 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(运行期违约通道——致命真的会死组) var db = ConfigDBScript.new() if not db.load_tables("res://data/tables", true): push_error("[run_time_fatal_test] 表加载失败——夹具不可用(回归)") diff --git a/game/tests/unit/core/save_boot_no_activate_test.gd b/game/tests/unit/core/save_boot_no_activate_test.gd index 7345c8e5..262b88b7 100644 --- a/game/tests/unit/core/save_boot_no_activate_test.gd +++ b/game/tests/unit/core/save_boot_no_activate_test.gd @@ -22,6 +22,7 @@ func _init() -> void: # ① 无参实例(autoload 实例化形态)=无激活态+默认合法空态(sv2 类级) func _test_no_arg_instance_has_no_activation() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(启动态无激活不读写组) var sm := SaveManagerScript.new() Kit.check_quiet(sm.slots.active_slot() == 0 and not sm.slots.has_write_target(), "sv2 无参实例无激活态(路径空/激活号 0/写盘目标空)〔卡① sv2〕") @@ -40,6 +41,7 @@ func _test_no_arg_instance_has_no_activation() -> void: # 判别锚:空 _path 的退化写目标=裸 ".tmp"(落 res://,拆闸实验实测杂散落点)—— # 断言先清退化目标再核零新增,闸短路缺陷(拒绝口旁路、写盘真实发生)必红。 func _test_write_rejected_without_target() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(无激活态写盘拒绝组) var sm := SaveManagerScript.new() var before := {} for i in range(1, 4): @@ -79,6 +81,7 @@ func _test_write_rejected_without_target() -> void: # (生产结算链全段在位:mark_first_adventure_done→apply_adventure_summary→refresh_treasure_unlocks # →write_to_disk;无激活态下落账内存、写盘拒绝、盘面零触碰——已知中间态的机检面) func _test_settle_chain_write_rejected() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(结算链写盘拒绝组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): Kit.check_quiet(false, "sv3 链测试前置:数值表加载失败〔卡① sv3〕") @@ -101,6 +104,7 @@ func _test_settle_chain_write_rejected() -> void: # ③ 越界槽号三原语拒绝+窥视越界返回不可读(原语边界 fail-safe) func _test_out_of_range_slots_rejected() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三存档位域界——越域槽拒绝组) var sm := SaveManagerScript.new() sm.slots.attach_slot_template("user://smoke_boot_noact_%d.json") Kit.check_quiet(not sm.slots.activate_slot(0) and not sm.slots.activate_slot(4) diff --git a/game/tests/unit/core/save_manager_domain_test.gd b/game/tests/unit/core/save_manager_domain_test.gd index 4574f1c9..bb47f190 100644 --- a/game/tests/unit/core/save_manager_domain_test.gd +++ b/game/tests/unit/core/save_manager_domain_test.gd @@ -46,6 +46,7 @@ func _fresh_rng(seed_val: int): # 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离, # 段首清场+段尾清理——绝不碰 user://save.json(红线)。 func _smoke_save() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176(存档域读写往返/迁移链/损坏兜底/域界组) print("--- SaveManager 存档读写/迁移/兜底 ---") var sm_script = load("res://core/save_manager.gd") var paths := ["user://smoke_save_roundtrip.json", "user://smoke_save_fresh.json", @@ -338,6 +339,7 @@ func _smoke_save() -> void: # 测试文件统一 user://smoke_save_slot_%d.json 模板隔离(attach_slot_template 注入口——禁各自拼槽名), # 段首清场+段尾清理——绝不碰 user://save.json 与真实槽 user://save_slot_N.json(红线)。 func _smoke_save_slots() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三槽位与摘要 cap——槽位域组) print("--- SaveManager 三存档位承载(迭代 33 卡①)---") var sm_script = load("res://core/save_manager.gd") var slot_tpl := "user://smoke_save_slot_%d.json" diff --git a/game/tests/unit/core/save_slot_isolation_test.gd b/game/tests/unit/core/save_slot_isolation_test.gd index 9d7070c8..6afdd4fe 100644 --- a/game/tests/unit/core/save_slot_isolation_test.gd +++ b/game/tests/unit/core/save_slot_isolation_test.gd @@ -38,6 +38,7 @@ func _write_v7_keystone(path: String, keystone: int) -> void: # ① 三槽往返:各槽写异值 → 写盘 → 新实例按位读回逐域相等;改槽 1 槽 2 字节不动(sv1) func _test_three_slot_roundtrip_and_isolation() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三槽位独立文件隔离组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) for i in range(1, 4): @@ -65,6 +66,7 @@ func _test_three_slot_roundtrip_and_isolation() -> void: # ② 高版本槽进档失败:拒绝+恢复切换前三态(路径/激活号/数据)+拒读槽零误写(sv9/sv10) func _test_activate_failure_restores_previous_slot() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(激活失败回滚组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_v7_keystone(TPL % 1, 311) @@ -86,6 +88,7 @@ func _test_activate_failure_restores_previous_slot() -> void: # ③ MetaProgression 宿主链切槽:引用同一性保持+写盘落当前槽(sv10——禁换实例先例) func _test_metaprogression_reference_identity() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176(元进度引用同一性——往返一致组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_v7_keystone(TPL % 1, 533) diff --git a/game/tests/unit/core/save_slot_summary_test.gd b/game/tests/unit/core/save_slot_summary_test.gd index 468045c9..b4354562 100644 --- a/game/tests/unit/core/save_slot_summary_test.gd +++ b/game/tests/unit/core/save_slot_summary_test.gd @@ -36,6 +36,7 @@ func _write_slot_json(path: String, content: String) -> void: # ① 三槽异值逐槽三项正确+不激活不写内存(sv7) func _test_peek_three_slots_distinct_values() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(槽位摘要按槽窥视组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) for i in range(1, 4): @@ -57,6 +58,7 @@ func _test_peek_three_slots_distinct_values() -> void: # ② 空槽零值合法态+不可读三态显式标记(sv7/契约 9) func _test_peek_empty_and_unreadable_states() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(坏槽三态组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) DirAccess.remove_absolute(TPL % 3) # 清①段残留——空槽夹具 @@ -77,6 +79,7 @@ func _test_peek_empty_and_unreadable_states() -> void: # ③ 窥视零副作用:字节+mtime 双比对不变+零 .corrupt.bak 改名(sv8——真实入口冒烟段同判据) func _test_peek_zero_side_effect() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(窥视零副作用组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_slot_json(TPL % 1, "corrupt bytes {{{{") diff --git a/game/tests/unit/core/settle_segments_test.gd b/game/tests/unit/core/settle_segments_test.gd index 4002bdd8..edebe932 100644 --- a/game/tests/unit/core/settle_segments_test.gd +++ b/game/tests/unit/core/settle_segments_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算五段 Segment——五段全序组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[settle_segments_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 75d66779..16e184c7 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -10,6 +10,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/跨局循环/秘宝.md:246(秘宝 12 件逐件行为——效果行为组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): Kit.check_quiet(false, "数值表加载") diff --git a/game/tests/unit/core/treasure_items_test.gd b/game/tests/unit/core/treasure_items_test.gd index 1560646d..c46ee17f 100644 --- a/game/tests/unit/core/treasure_items_test.gd +++ b/game/tests/unit/core/treasure_items_test.gd @@ -53,6 +53,7 @@ func _all_revealed(_c: Vector2i) -> bool: func _init() -> void: +# oracle: design/gdd/systems/跨局循环/秘宝.md:246(秘宝结构冻结与护栏校验——结构组) # ① 授予归因+受理序+Used 不可逆 var inv = _new_inv() var r1: Dictionary = inv.add_item("sapper_order", "safe_cell", Vector2i(3, 4), 2) diff --git a/game/tests/unit/core/tree_requires_level_test.gd b/game/tests/unit/core/tree_requires_level_test.gd index 8b189ab7..1434ce5a 100644 --- a/game/tests/unit/core/tree_requires_level_test.gd +++ b/game/tests/unit/core/tree_requires_level_test.gd @@ -7,6 +7,7 @@ extends SceneTree func _init() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:195(等级阈值前置:schema 四拒一过+判定三态+投影两态组) var failures := 0 var db = load("res://core/config_db.gd").new() var mt = load("res://data/schemas/meta_tree_table.gd").new() diff --git a/game/tests/unit/core/tree_rules_v11_test.gd b/game/tests/unit/core/tree_rules_v11_test.gd index 8c791eb7..834881ef 100644 --- a/game/tests/unit/core/tree_rules_v11_test.gd +++ b/game/tests/unit/core/tree_rules_v11_test.gd @@ -12,6 +12,7 @@ extends SceneTree func _init() -> void: +# oracle: design/gdd/systems/跨局循环/天赋树.md:194(结构封闭:六键合成行闭集校验组) var eng = load("res://core/effect_engine.gd") var tree_tbl = load("res://data/schemas/meta_tree_table.gd") var db = load("res://core/config_db.gd").new() @@ -88,6 +89,7 @@ func _init() -> void: # ---- 规则键参数域校验(架构整改批 R1——六键参数字典):正例全过/缺参数拒/多参数拒/越域拒/非字典拒 ---- func _test_rule_key_params(eng, tree_tbl, db) -> Array: +# oracle: design/gdd/systems/跨局循环/天赋树.md:194(规则键参数字典——参数名/取值域合规组) var errs: Array = [] var params_tbl: Dictionary = tree_tbl.RULE_KEY_PARAMS # 正例:meta_tree.tres 现行 Fork 行逐键过 validate(加载期已验——此处显式复核) From 9e26df16be3a1ff740e75c2f53fb49e8e0df9f01 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:23:25 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B03/7=C2=B7battle+contracts+i?= =?UTF-8?q?ntegration):=2020=E5=A5=97=E4=BB=B680=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E9=80=90=E5=87=BD=E6=95=B0=E6=8C=82#=20oracle=E6=B3=A8?= =?UTF-8?q?=E8=AE=B0=E2=80=94=E2=80=94=E6=88=98=E6=96=97=E5=9B=9B=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E9=AA=8C=E6=94=B6=E5=8F=A5(=E6=88=98=E6=96=97?= =?UTF-8?q?=E7=BB=93=E7=AE=97:360-366/=E6=95=8C=E4=BA=BA=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?:295-311/=E5=A2=9E=E7=9B=8A=E7=8A=B6=E6=80=81:250-262/=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E6=88=98=E6=96=97=E8=B5=84=E6=BA=90:235-241)+?= =?UTF-8?q?=E6=8E=92=E9=9B=B7=E7=8E=A9=E6=B3=95:231/237+=E8=A1=8C=E5=8A=A8?= =?UTF-8?q?=E7=BB=8F=E6=B5=8E+=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82:156-158?= =?UTF-8?q?+=E5=A5=87=E7=89=A9+=E5=AD=98=E6=A1=A3=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8C=96=E6=96=AD=E7=82=B9=E5=9F=9F/=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E9=93=BE+=E5=85=B3=E5=8D=A1=E8=8A=82=E7=82=B9=E5=9B=BE?= =?UTF-8?q?=E7=94=9F=E6=88=90:132+architecture:89/90/107/144+code-conventi?= =?UTF-8?q?ons:40/41/54;=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20?= =?UTF-8?q?=E5=85=AD=E7=9B=AE=E5=BD=95=E4=BB=A3=E8=A1=A8=E5=A5=97=E4=BB=B6?= =?UTF-8?q?=E7=9B=B4=E8=B7=91=E5=85=A8=E7=BB=BF(seal/contracts=C3=972/inte?= =?UTF-8?q?gration=C3=973),=E5=85=B6=E4=BD=99=E9=9A=8FWP4=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E6=A1=A3=E5=85=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/cross_system_flow_test.gd | 4 ++++ .../integration/node_map_determinism_test.gd | 1 + .../integration/save_rng_roundtrip_test.gd | 2 ++ .../unit/battle_settlement/boss_gate_test.gd | 2 ++ .../damage_controller_test.gd | 6 +++++ .../battle_settlement/damage_math_test.gd | 5 ++++ .../battle_settlement/effect_port_test.gd | 4 ++++ .../extended_row_violation_test.gd | 1 + .../battle_settlement/refresh_feats_test.gd | 13 ++++++++++ .../battle_settlement/roll_multiplier_test.gd | 4 ++++ .../round_flow_skill_pool_test.gd | 5 ++++ .../battle_settlement/seal_polarity_test.gd | 16 +++++++++++++ .../settlement_wiring_snapshot_test.gd | 1 + .../battle_settlement/skill_unlock_test.gd | 4 ++++ .../battle_settlement/trigger_cond_test.gd | 1 + .../variant_placement_test.gd | 4 ++++ .../tests/unit/contracts/source_guard_test.gd | 2 ++ .../unit/contracts/system_contracts_test.gd | 24 +++++++++++++++++++ 18 files changed, 99 insertions(+) diff --git a/game/tests/integration/cross_system_flow_test.gd b/game/tests/integration/cross_system_flow_test.gd index 165ae097..22c1b78d 100644 --- a/game/tests/integration/cross_system_flow_test.gd +++ b/game/tests/integration/cross_system_flow_test.gd @@ -46,6 +46,7 @@ func _fresh_rng(seed_val: int): # ---- ① RNG 事件日志会话落盘(源 `_initialize` :70,主会话裁决一改判集成—— # 驱动沿源 :60–70 取号序列自持:master 种子 42 → 前置取号 → new_adventure 开日志会话 → 会话内取号)---- func _rng_event_log_session() -> void: +# oracle: design/engineering/architecture/architecture.md:90(事件日志落盘会话组) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) rng.open_stream("board_gen") @@ -60,6 +61,7 @@ func _rng_event_log_session() -> void: # _on_level_finished(victory)+breakpoint_snapshot 取读+clear_breakpoint; # 起局与兑付两步的受理断言留单元宿主(adventure_feats_test),此处纯驱动——前置失败自然落红)---- func _feats_breakpoint_roundtrip() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(专长断点附随件往返组) var rt := FeatTable.new() rt.rows = [ {"feat_id": "x1", "display_name": "试一", "rarity": "common", "desc": "测试揭示", "cap": 1, @@ -105,6 +107,7 @@ func _feats_breakpoint_roundtrip() -> void: # level_autoplay 真局摘要 → apply 原语 → 域读数 → 写盘读回保持;临时档 user://smoke_save_integration.json # 段首清场+段尾清理(源 _smoke_save 隔离纪律),绝不碰 user://save.json(红线))---- func _save_summary_integration() -> void: +# oracle: design/gdd/systems/跨局循环/元进度.md:150(摘要集成与钥石只增不减组) var sm_script = load("res://core/save_manager.gd") var path9 := "user://smoke_save_integration.json" # 源 paths[8] if FileAccess.file_exists(path9): @@ -137,6 +140,7 @@ func _save_summary_integration() -> void: # ---- ④ 试玩记录仪(源 `_smoke_playtest_logger` 整套八条,主会话裁决七改判集成—— # PlaytestLogger 挂真实结算流:会话文件/逐结算快照/字段齐备/spawn_count 回归/诊断转储/JSONL 回读)---- func _smoke_playtest_logger() -> void: +# oracle: design/engineering/code-conventions.md:40(试玩日志器跨系统接线组) print("--- PlaytestLogger(结算快照 JSONL + 诊断转储)---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260912) diff --git a/game/tests/integration/node_map_determinism_test.gd b/game/tests/integration/node_map_determinism_test.gd index 773fce03..0ddcd4cb 100644 --- a/game/tests/integration/node_map_determinism_test.gd +++ b/game/tests/integration/node_map_determinism_test.gd @@ -33,6 +33,7 @@ func _init() -> void: # ---- 关卡节点图生成·确定性承载断言面 ---- func _smoke_generated_map_determinism() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(起关/选路/断点重生成同图组) # ① 纯函数复现:图 = f(参数档, 种子)——同档同种子两次产图逐位同图,换种子必变图 var gen = NodeMapGenerator.new(_fresh_rng(910001)) var params2: Dictionary = _db.run.level_gen_params.row_by_level_index(2) diff --git a/game/tests/integration/save_rng_roundtrip_test.gd b/game/tests/integration/save_rng_roundtrip_test.gd index 263b7842..ade5b288 100644 --- a/game/tests/integration/save_rng_roundtrip_test.gd +++ b/game/tests/integration/save_rng_roundtrip_test.gd @@ -35,6 +35,7 @@ func _init() -> void: # ---- 用例一:盘上往返保真——写档再读回,断点逐键零漂移 ---- func _test_disk_roundtrip_preserves_breakpoint() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(存档⇄RNG 快照往返——断点域逐位组) var rng := RngServiceScript.new() var sm := SaveManagerScript.new(SAVE_PATH) var adr := AdventureRunnerScript.new(_db, rng) @@ -65,6 +66,7 @@ func _test_disk_roundtrip_preserves_breakpoint() -> void: # ---- 用例二:旧版本档经迁移链后仍可续玩(单测不覆盖的跨系统集成面)---- func _test_old_version_save_migrates_and_resumes() -> void: +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:179(旧档迁移链读回续玩组) var rng := RngServiceScript.new() var sm := SaveManagerScript.new(SAVE_PATH) var adr := AdventureRunnerScript.new(_db, rng) diff --git a/game/tests/unit/battle_settlement/boss_gate_test.gd b/game/tests/unit/battle_settlement/boss_gate_test.gd index bf32d8c4..6691a46d 100644 --- a/game/tests/unit/battle_settlement/boss_gate_test.gd +++ b/game/tests/unit/battle_settlement/boss_gate_test.gd @@ -8,11 +8,13 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 节点开战行集宿主(装载组) _test_boss_node_spawns_boss_rowset() quit(Kit.finish("boss_gate_test")) func _test_boss_node_spawns_boss_rowset() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:295 开战生成按遭遇坐标取行集(Boss 行集组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 Kit.check("前置:表加载零 error", load_ok) diff --git a/game/tests/unit/battle_settlement/damage_controller_test.gd b/game/tests/unit/battle_settlement/damage_controller_test.gd index d2cebb7e..250a3cda 100644 --- a/game/tests/unit/battle_settlement/damage_controller_test.gd +++ b/game/tests/unit/battle_settlement/damage_controller_test.gd @@ -23,6 +23,7 @@ var _rows: Array = [] # 当前 run 的行协议收集(_on_row 绑定法— func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 伤害单据编排宿主(装载组) var 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) @@ -70,6 +71,7 @@ func _row_by_display_prefix(prefix: String) -> Dictionary: # ---- 五配方一次全流程 + 双跑逐位复现 ---- func _run_recipe_sequence(db) -> Array: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(五段全序——配方序列组) _rows = [] var h = _harness(db) var units = h.units @@ -176,6 +178,7 @@ func _run_recipe_sequence(db) -> Array: func _test_five_recipes_and_replay(db) -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:363(同种子回放逐行相同组) var run1: Array = _run_recipe_sequence(db) Kit.check("五配方序列产出 5 行伤害行", run1.size() == 5) var run2: Array = _run_recipe_sequence(db) @@ -185,6 +188,7 @@ func _test_five_recipes_and_replay(db) -> void: # ---- A 模式(开单即结——回切位) ---- func _test_policy_immediate_mode(db) -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算策略即时面组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -200,6 +204,7 @@ func _test_policy_immediate_mode(db) -> void: # ---- 来源敌死亡:队列中未结单据作废(死亡即不动手) ---- func _test_source_death_void(db) -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:133(摇定后兑现前被杀本拍作废组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -233,6 +238,7 @@ func _unit_fixture() -> Dictionary: func _test_identical_content_dual_enemy(db: ConfigDb) -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:303(双敌同内容顺位组) var a := _unit_fixture() var b := _unit_fixture() Kit.check("前置:双敌字典内容全同且非同一实例", a == b and not is_same(a, b)) diff --git a/game/tests/unit/battle_settlement/damage_math_test.gd b/game/tests/unit/battle_settlement/damage_math_test.gd index db07970d..e5d9114f 100644 --- a/game/tests/unit/battle_settlement/damage_math_test.gd +++ b/game/tests/unit/battle_settlement/damage_math_test.gd @@ -12,6 +12,7 @@ var db func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(伤害公式宿主——装载组) db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("前置:settlement 表加载零 error", load_ok and db.diagnostics_count("error") == 0) @@ -30,6 +31,7 @@ func _floor_val() -> int: # ---- F2 乘区:池内加算 → 池间乘算 → floor(单一取整规则)---- func _test_pool_damage() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(池伤害公式组) var d := _floor_val() + 9 # 表值推导的任意正探针量(非玩法行值) Kit.check("中性双池 (0,0) 原样透传", DamageMathScript.pool_damage(d, 0, 0) == d) Kit.check("负敌方池压低不增高", DamageMathScript.pool_damage(d, 0, int(db.combat.settlement.core.pool_clamp_lo)) <= d) @@ -42,6 +44,7 @@ func _test_pool_damage() -> void: # ---- 克制分量:空桶不吃修正恒 0;落地下限保底([1,+∞) 全来源)---- func _test_counter_component() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(计数器分量组) var floor_v := _floor_val() Kit.check("空桶(点数≤0)恒 0 不吃修正", DamageMathScript.counter_component(0, floor_v, floor_v) == 0 and DamageMathScript.counter_component(-1, 5, floor_v) == 0) @@ -53,6 +56,7 @@ func _test_counter_component() -> void: # ---- 掷点折扣查档(纯函数):满阈值恒全额;档位边界/末档语义与表数组一致 ---- func _test_roll_discount_percent() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(乘区折扣组) var threshold := 10 # 查档探针阈值(夹具值——档位参数本身全部来自表) var bounds: Array = db.combat.settlement.damage_roll.roll_tier_bounds var discounts: Array = db.combat.settlement.damage_roll.roll_tier_discounts @@ -77,6 +81,7 @@ func _test_roll_discount_percent() -> void: # ---- 吃掷点分量算术:折扣→(暴击)→floor→+克制修正(单一取整链)---- func _test_rolled_component() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(掷点分量组) var base := float(_floor_val() + 7) # 表值推导探针基线 var crit_mult: int = int(db.combat.settlement.damage_roll.crit_multiplier) Kit.check("满额折扣无暴击 = floor(基线) + 修正 0", DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base))) diff --git a/game/tests/unit/battle_settlement/effect_port_test.gd b/game/tests/unit/battle_settlement/effect_port_test.gd index 4790e064..a8948a1e 100644 --- a/game/tests/unit/battle_settlement/effect_port_test.gd +++ b/game/tests/unit/battle_settlement/effect_port_test.gd @@ -19,6 +19,7 @@ var _db func _init() -> void: +# oracle: design/engineering/architecture/architecture.md:144(效果端口宿主——装载组) _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) @@ -53,6 +54,7 @@ func _hero_attack_value(rows: Array) -> int: # ---- V1 端口单实例与真实发射路由 ---- func _smoke_effect_port_wiring() -> void: +# oracle: design/engineering/architecture/architecture.md:144(端口单实例与六 Callable 路由组) print("--- 效果端口:装配链单实例+真实发射路由 ---") # 充能跑(蓄力周转 charge_rotation 在册——attack_commit 订阅行 attack_bonus per 蓄力周转计数器) var orch_c = _orch(TEST_SEED) @@ -86,6 +88,7 @@ func _smoke_effect_port_wiring() -> void: # ---- V2 业务类零引擎直连(源码契约锚——theme_projection_test 源码锚惯例)---- func _test_business_classes_engine_free() -> void: +# oracle: design/engineering/architecture/architecture.md:144(业务类零引擎直连组) print("--- 效果端口:业务类零引擎直连源码锚 ---") for path in ["res://core/damage_controller.gd", "res://core/action_economy.gd"]: var src := FileAccess.get_file_as_string(path) @@ -97,6 +100,7 @@ func _test_business_classes_engine_free() -> void: # ---- V3 伤害域事实面闭集(合成行两对照——damage_taken 真实单据流)---- func _test_biz_fact_face_closed() -> void: +# oracle: design/engineering/architecture/architecture.md:144(闭面四键 biz_fact 组) print("--- 效果端口:业务发射事实面闭集 ---") var orch = _orch(TEST_SEED) Kit.check("V3 begin_battle 受理", orch.begin_battle(_enc([]))) diff --git a/game/tests/unit/battle_settlement/extended_row_violation_test.gd b/game/tests/unit/battle_settlement/extended_row_violation_test.gd index 0198c410..827eed8b 100644 --- a/game/tests/unit/battle_settlement/extended_row_violation_test.gd +++ b/game/tests/unit/battle_settlement/extended_row_violation_test.gd @@ -11,6 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(扩展行必载 effect_id——加载致命组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): push_error("[extended_row_violation_test] 表加载失败") diff --git a/game/tests/unit/battle_settlement/refresh_feats_test.gd b/game/tests/unit/battle_settlement/refresh_feats_test.gd index 7b7932c9..958427cb 100644 --- a/game/tests/unit/battle_settlement/refresh_feats_test.gd +++ b/game/tests/unit/battle_settlement/refresh_feats_test.gd @@ -154,6 +154,7 @@ func _seal_direct_with_expansion(orch) -> Vector2i: # 四条真实路径(敌方攻击/危险格/错误封印/地形)逐路驱动:每条至少一张主角承伤单据在案, # 且承伤计数器 == 主角承伤单据行数(每单据恰发射一次的全量判据——重复发射即计数器>行数)。 func test_all_hero_damage_paths_emit_once() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图生存:全局固定减伤全伤害路径恰一次组) print("--- V5 全局 damage_taken:四路径逐路恰一次 ---") # ① 敌方攻击路径(Boss 战零挖掘连停三轮——干净样本:零地形零资源单据;多轮保证伤害技在案) var orch1 = _orch(46001) @@ -223,6 +224,7 @@ func _plan_seal(orch, target: Vector2i) -> Dictionary: # 同一伤害不得出现两次消费(重复发射=计数器>行数):敌方路径与地形路径两样本逐位核对—— # 地形单据带 hero_damage_consumed 标记,战斗段末 _resolve_board_flat_hero 跳过二次发射由此钉住。 func test_no_duplicate_damage_taken_event() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(受击事件零重复组) print("--- V5 全局 damage_taken:无重复消费 ---") var orch1 = _orch(46011) var rows1 := _collect_hero_rows(orch1) @@ -264,6 +266,7 @@ func _auto_dig(orch) -> void: # V2 矿脉储能:受理的付费挖格 RAP 恰 +1、AP 按表扣、板资源不变 func test_reserve_on_accepted_dig() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(换图基座:挖格产 RAP 组) print("--- V2 矿脉储能:付费挖格产 RAP ---") var orch = _orch(46021) Kit.check("V2 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve"]))) @@ -301,6 +304,7 @@ func _drain_round_rap_accounting(orch) -> bool: # V3 省力周转:折价门槛与扣费(can 与执行同源;base 8→扣 4) func test_refresh_discount_gate_and_charge() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(刷新折扣门控与充能组) print("--- V3 省力周转:折价门槛与扣费 ---") var orch = _orch(46022) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["easy_swap"]))) @@ -322,6 +326,7 @@ func test_refresh_discount_gate_and_charge() -> void: # V3 双基座叠加:挖格产 RAP 与刷新折价同场生效 func test_double_base_stack() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(双基座叠加组) print("--- V3 双基座叠加 ---") var orch = _orch(46023) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve", "easy_swap"]))) @@ -338,6 +343,7 @@ func test_double_base_stack() -> void: # V12(refresh_feats 侧):无专长基线——挖格零产 RAP、刷新按表基线扣、AP 保留、上限照旧 func test_no_feats_baseline() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(零专长基线组) print("--- V12 无专长基线(refresh_feats 侧) ---") var orch = _orch(46024) Kit.check("V12 前置:战斗受理", orch.begin_battle(_enc([]))) @@ -373,6 +379,7 @@ func _br_count(orch) -> int: # V4 成功事实只在新板就位后发射:成功恰一次;非 Active/每轮上限/RAP 不足三拒面全零事实零换图 func test_board_refresh_fact_only_on_success() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(弃图刷新事实面仅成功态组) print("--- V4 成功刷新事实:拒绝零事实 ---") _install_br_probe() var orch = _orch(46031) @@ -407,6 +414,7 @@ func test_board_refresh_fact_only_on_success() -> void: # V10 矿道红利:每次成功刷新 escrow 恰 +1 且 fx_grant_gp 行在案;拒绝零变化 func test_mine_profit_gp() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图资源:刷新固定 GP 收益组) print("--- V10 矿道红利:刷新固定 GP ---") var orch = _orch(46032) var gp_rows := {"n": 0} @@ -453,6 +461,7 @@ func _damage_rows_by_source(rows: Array, source: String) -> Array: # V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减; # 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射) func test_stone_mitigation_all_damage() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(全局减伤全覆盖组) print("--- V6 石层缓冲:全局固定减伤 ---") # S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死) var orch1 = _orch(46041) @@ -508,6 +517,7 @@ func test_stone_mitigation_all_damage() -> void: # V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入; # 自然累计 2 与满血未生效两形态分战场钉死) func test_rest_rotation_cap_and_clear() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(轮末恢复 cap 与清账组) print("--- V9 坑道小憩:轮末恢复 ---") # R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3 var orch1 = _orch(46051) @@ -565,6 +575,7 @@ func test_rest_rotation_cap_and_clear() -> void: # V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空) func test_charge_layers_consumed_same_round() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(充能当轮消耗组) print("--- V8 蓄力周转:攻击消费层 ---") var orch1 = _orch(46061) var rows1 := _collect_damage_rows(orch1) @@ -591,6 +602,7 @@ func test_charge_layers_consumed_same_round() -> void: # V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账) func test_blast_defers_to_combat_segment() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(刷新群伤让位战斗段组) print("--- V7 崩壁冲击:延迟群伤 ---") var orch = _orch(46071) var rows := _collect_damage_rows(orch) @@ -609,6 +621,7 @@ func test_blast_defers_to_combat_segment() -> void: # 七件 validate/在册/稀有度 cap 对表/跨表计数器引用闭合/desc 禁词 func test_seven_feats_validate_and_pool() -> void: +# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图系七件校验与池组) print("--- V11 七件数据与池 ---") var seven := {"vein_reserve": "excellent", "easy_swap": "excellent", "wall_blast": "excellent", "charge_rotation": "excellent", "stone_mitigation": "common", "tunnel_rest": "common", "mine_profit": "rare"} diff --git a/game/tests/unit/battle_settlement/roll_multiplier_test.gd b/game/tests/unit/battle_settlement/roll_multiplier_test.gd index 723f8129..3096787f 100644 --- a/game/tests/unit/battle_settlement/roll_multiplier_test.gd +++ b/game/tests/unit/battle_settlement/roll_multiplier_test.gd @@ -19,6 +19,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(掷点与乘区宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -43,6 +44,7 @@ func _init() -> void: # 受理后不变量(encounter 受理后只读)。攻击单 base_value 贯通由 _smoke_damage_types 的 # hero_atk_val=100 全链路行值承载(同夹具既有覆盖——派生读在攻击链上工作即行值随 override 走)---- func _smoke_hero_atk_derived() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(英雄攻击派生组) var rng_a = load("res://core/rng_service.gd").new() rng_a.set_master_seed(4242) var s_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a) @@ -65,6 +67,7 @@ func _smoke_hero_atk_derived() -> void: # → heavy 伤害 == light −1、ward == light +1(要求基线伤害 ≥ 2——地板 1 会吞 −1 位移)。 # 承伤位移红线(卡片 §8 L3):全局位移只允许 ±1 量级——非此量级 = 矩阵消费面有未盘点的第二落点(停机)。 func _smoke_enemy_attack_type() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:295(敌攻击类型行集组) print("--- 敌方攻击类型轴(迭代 24 N3)---") # ① 表列:20 行全填 normal(N3 全填不做差异化——Q9) var at_ok := true @@ -123,6 +126,7 @@ func _smoke_enemy_attack_type() -> void: # ---- 护甲等级掷点·行为面(折扣档分布 / nat20 暴击 / 伤害下限 / 棋盘来源必中)---- func _smoke_armor_roll() -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:254(护甲掷点与叠层合并组) print("--- 护甲等级掷点(查档纯函数 + 暴击 + 伤害下限 + AC12 分布 + 棋盘必中)---") # ① 查档纯函数:全掷点域逐位对照(AC10 / AC12)+ EV 锚(AC10 期望减免 21.25% —— registry 参考数) var expect_ac10 := {100: 11, 75: 3, 50: 4, 25: 2} # 掷 10-20 全额 / 7-9 / 3-6 / 1-2 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 9fd663be..3f2fe528 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 @@ -22,6 +22,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(轮内流与技能池宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -47,6 +48,7 @@ func _init() -> void: # ---- 完整战斗回放(自动玩家,确定性)——回放断言:同种子两遍步骤流逐行相同 ---- func _smoke_full_battle(L: int, D: int, label: String) -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(全战斗回放——五段全序组) print("--- 完整战斗回放:%s ---" % label) var run1 = _run_battle(L, D) var run2 = _run_battle(L, D) @@ -81,6 +83,7 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(战斗驱动组) # 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -145,6 +148,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void: # ---- 行动经济·轮内刷新冒烟(方案 A:Active 相位当场换图继续挖)---- func _smoke_active_refresh() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:262(停手转存储备与轮内刷新组) print("--- 行动经济·轮内刷新(方案 A)---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260912) @@ -251,6 +255,7 @@ func _active_refresh_replay() -> Array: # ---- 遭遇随机化冒烟(难度曲线与Boss Core Rule 8:区间骰/encounter 流/可回放/空表定值路径)---- func _smoke_encounter_roll() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:296(遭遇区间骰——摇号确定性组) print("--- 遭遇随机化(unit_count 区间骰)---") var counts: Array = [] for i in range(20): diff --git a/game/tests/unit/battle_settlement/seal_polarity_test.gd b/game/tests/unit/battle_settlement/seal_polarity_test.gd index 547f394a..70178b65 100644 --- a/game/tests/unit/battle_settlement/seal_polarity_test.gd +++ b/game/tests/unit/battle_settlement/seal_polarity_test.gd @@ -22,6 +22,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印与极性宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -45,6 +46,7 @@ func _init() -> void: # ---- 末点 AP 封印轮末结算(playtest 缺陷②回归:末点 AP 花在封印必须终结轮次进入下一轮)---- func _smoke_seal_ap_exhausted() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:260(末点 AP 耗尽即收轮组) print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260911) @@ -322,6 +324,8 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array: func _smoke_clear_reward_chain() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(清图奖励链——清图槽对象身份组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(清图奖励链——清图槽对象身份组) print("--- 清图奖励·双份兑付链(完美封印 / 零兑付 / 第二份被拒 / 高光记实)---") # ① 真实流完美封印:标中一面真雷封印 + 零误标 + 清全图 → grant_clear 身份(clear_blast)双份真实兑付 var rows: Array = [] @@ -350,6 +354,8 @@ func _smoke_clear_reward_chain() -> void: # 弱形式登记:正常战斗流下"授段时刻无存活敌"不可达——敌全灭当轮判定段即判胜并终止战斗,而清图批在判定段之前, # 恒有存活敌;故本组直调编排私有面构造状态,只断言"路径存在 + 行为符合契约"(构造手法见各行注释)。 func _smoke_clear_reward_zero_paths() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240(无存活敌群伤记「奖励未生效」组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240(无存活敌群伤记「奖励未生效」组) # ②-a 首份零兑付判定(无存活敌)→ first_ok=false + 未生效行 var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260915) @@ -404,6 +410,8 @@ func _smoke_clear_reward_zero_paths() -> void: # 轮内刷新 provenance 守卫:刷新换来的图同轮清图不发清图奖励(_board_from_midround_refresh 守卫) func _smoke_refresh_provenance_guard() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237(换图重置:旧封印印记先判定后消失组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237(换图重置:旧封印印记先判定后消失组) print("--- 轮内刷新 provenance 守卫(刷新图清图不发清图奖励)---") var rows: Array = [] var run := _run_full_clear(20260916, true, rows) @@ -430,6 +438,8 @@ func _smoke_refresh_provenance_guard() -> void: func _smoke_polarity_routing() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:366(极性路由:正向只兑正向、负向只兑负向组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:366(极性路由:正向只兑正向、负向只兑负向组) print("--- 极性路由(挖安全格正向 / 挖危险格负向 / 标中正向 / 标空负向)---") # 战斗随机值面随全局序漂移——构造种子按行为面扫描(谓词见 _polarity_probe_accepts) var probe := {} @@ -567,6 +577,8 @@ func _smoke_polarity_routing() -> void: # 场景 B(②):标危险格 → 直接挖开 → 收轮。 # 场景 C(①③):标安全格 + 标危险格 → 不挖收轮 → 标空/标中判定照常。 func _smoke_seal_irreversible_quadrants() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印不可逆四情况组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印不可逆四情况组) print("--- 封印不可逆四情况(禁撤封印 + 挖封印格兑付改判)---") # 场景 A:安全格直挖(情况④)+ seal_pull/重复封印拒绝面 var rng_a = load("res://core/rng_service.gd").new() @@ -675,6 +687,8 @@ func _smoke_seal_irreversible_quadrants() -> void: # 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 grant_routed 负向路由(att 归因束形态), # 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。 func _smoke_negative_dictionary() -> void: +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(下轮行动点修正排队与兑现组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256(下轮行动点修正排队与兑现组) print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 警觉 / action 行动点)---") # 数据面:sapped 行在位(状态表七行 + effect_pool_map 映射在位) var sapped: Dictionary = _db.combat.status_effects.row_by_id("sapped") @@ -768,6 +782,8 @@ func _smoke_negative_dictionary() -> void: # 资源面断言:chain/dig 事件恒携带 resource=true(危险格同权);initial 恒 false(封锁格); # 封锁格查询 = null、危险格未开恒可查。 func _smoke_resource_surface() -> void: +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(资源面只读查询组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(资源面只读查询组) print("--- 格面资源资格面(危险格携带 / 封锁格无资源)---") var run1 = _run_battle(1, 0) var run2 = _run_battle(2, 0) diff --git a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd index 3c5d16db..934c1aaf 100644 --- a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd +++ b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd @@ -419,6 +419,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算接线同种子逐项 golden 对拍组) _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) diff --git a/game/tests/unit/battle_settlement/skill_unlock_test.gd b/game/tests/unit/battle_settlement/skill_unlock_test.gd index dabb76d4..53005034 100644 --- a/game/tests/unit/battle_settlement/skill_unlock_test.gd +++ b/game/tests/unit/battle_settlement/skill_unlock_test.gd @@ -13,6 +13,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:293 Boss 半血解锁技宿主(装载组) var 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) @@ -43,6 +44,7 @@ func _has_sid(arr: Array, sid: String) -> bool: func _test_gate_predicate(db) -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(门谓词:条件不满足不进可摇集合组) var ec = _boss(db, 2, 9) var u: Dictionary = _boss_u(ec) var mhp: int = int(u.max_hp) @@ -71,6 +73,7 @@ func _test_gate_predicate(db) -> void: # 闩不可逆:解锁后治疗抬回满血 → 复查零新解锁、可摇集合不回退 func _test_latch_irreversible(db) -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(闩型条件首次为真整场保持组) var ec = _boss(db, 2, 9) var u: Dictionary = _boss_u(ec) ec.apply_damage(u, int(u.max_hp) / 2) # → 半血触发 @@ -82,6 +85,7 @@ func _test_latch_irreversible(db) -> void: # 普通行无字段逐位零扰动:地精 (1,0) 池快照零 unlock 键、过滤恒空转、可摇=全池 func _test_plain_rows_zero_drift(db) -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:301(普攻倍率 1 特例——零漂移组) var ec = load("res://core/unit_container.gd").new(db) Kit.check("零扰动前置:巨蜘蛛首遇带 spawn(池三行全无 trigger——快照键策略钉死载体)", ec.spawn({"level_index": 2, "node_depth": 1, "hero_hp": int(db.combat.hero.max_hp)})) var u: Dictionary = ec.all_enemies()[0] diff --git a/game/tests/unit/battle_settlement/trigger_cond_test.gd b/game/tests/unit/battle_settlement/trigger_cond_test.gd index fc6eb2c7..fd6347ae 100644 --- a/game/tests/unit/battle_settlement/trigger_cond_test.gd +++ b/game/tests/unit/battle_settlement/trigger_cond_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(触发条件机制 V1–V10 组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[trigger_cond_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/battle_settlement/variant_placement_test.gd b/game/tests/unit/battle_settlement/variant_placement_test.gd index 620c3be3..b8963d6b 100644 --- a/game/tests/unit/battle_settlement/variant_placement_test.gd +++ b/game/tests/unit/battle_settlement/variant_placement_test.gd @@ -15,6 +15,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:151 变异布置宿主(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -53,6 +54,7 @@ class _CountVariantRng extends RngServiceCore: # 本段经 begin_battle 走完整初始化序后断言:门控一致性 / 布置域 / 数量公式 / 连通片上限 / 确定性。 func _smoke_variant_placement(L: int) -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:63(战内布置段门控组) print("--- 地形变异 布置段(L%d)---" % L) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -146,6 +148,7 @@ func _variant_dump(b) -> String: func _smoke_global_variant() -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:74(全局替换制与回落组) print("--- 全局作用域变异(门控三维 + 全局池空兜底回落 + 资源面偏斜)---") # ① 跨字段互锁负样本(resource_face ⟺ scope=global 三条 + effect_value 新域 [0,50]) var vt = load("res://data/schemas/variation_table.gd").new() @@ -340,6 +343,7 @@ func _errs_has_cross_field(errs: Array) -> bool: # 全称不可断)。可达判据=①静态(布置面零字面量下界)+②同种子改值产物互异(真实入口=begin_battle 布置段)。 func _test_pack_min_from_table() -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:63(连通片格数 [pack_min,pack_max] 读表组) # ① 静态:布置面零字面量下界(randi_range(2, 退役——下界读 pack_min 表头单点) var src := FileAccess.get_file_as_string("res://core/board_installer.gd") Kit.check("布置面零字面量下界(randi_range(2, 退役、下界读表单点在位)〔架构整改 V10·静态〕", diff --git a/game/tests/unit/contracts/source_guard_test.gd b/game/tests/unit/contracts/source_guard_test.gd index 0b8a60aa..86822e4f 100644 --- a/game/tests/unit/contracts/source_guard_test.gd +++ b/game/tests/unit/contracts/source_guard_test.gd @@ -21,6 +21,7 @@ func _init() -> void: # ---- 类型闸断言(V7——project.godot warning 配置在位〔源码断言〕+审计器脚本在位; # 审计器归零判定走 static_scan S9 闸机,此处兜配置面)---- func _smoke_type_gate() -> void: +# oracle: design/engineering/code-conventions.md:41(封装边界——类型闸源码扫描组) var pj: String = FileAccess.get_file_as_string("res://project.godot") Kit.check("V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕", pj.contains("gdscript/warnings/untyped_declaration=1")) @@ -31,6 +32,7 @@ func _smoke_type_gate() -> void: # ---- 卫生件 grep 闸(V8——死代码/字符串 Callable/影子账残留零命中;源码扫描面)---- func _smoke_hygiene_gates() -> void: +# oracle: design/engineering/code-conventions.md:54(模态覆盖层层级与登记纪律——卫生闸组) var callables := 0 for d in ["res://core", "res://presentation", "res://data"]: var dir := DirAccess.open(d) diff --git a/game/tests/unit/contracts/system_contracts_test.gd b/game/tests/unit/contracts/system_contracts_test.gd index 350e1308..a5d3cc0b 100644 --- a/game/tests/unit/contracts/system_contracts_test.gd +++ b/game/tests/unit/contracts/system_contracts_test.gd @@ -40,6 +40,8 @@ func _init() -> void: # ---- B1 兑付配方词表:五配方/三目标规则/双结账政策——常量非空且互异(封闭枚举负空间) ---- func _test_damage_controller_vocabularies() -> void: +# oracle: design/engineering/architecture/architecture.md:89(事件/事实/效果类型/算子四词表封闭——契约词表组) +# oracle: design/engineering/architecture/architecture.md:89(事件/事实/效果类型/算子四词表封闭——契约词表组) var kinds: Array = [ DamageControllerScript.KIND_HERO_ATTACK, DamageControllerScript.KIND_ENEMY_SKILL, DamageControllerScript.KIND_BOARD_TYPED, DamageControllerScript.KIND_BOARD_BLAST, @@ -69,6 +71,8 @@ func _test_damage_controller_vocabularies() -> void: # ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ---- func _test_rng_tag_closure(db) -> void: +# oracle: design/engineering/architecture/architecture.md:90(流 scope 封闭词表组) +# oracle: design/engineering/architecture/architecture.md:90(流 scope 封闭词表组) var stream_label: String = String(db.combat.settlement.damage_roll.armor_roll_stream) Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)", RngServiceScript.PURPOSE_TAGS.has(stream_label)) @@ -82,6 +86,8 @@ func _test_rng_tag_closure(db) -> void: # ---- B1→D1 行协议:单据九栏缺省齐备 + 协议公共键形态 ---- func _test_ticket_nine_columns() -> void: +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(单据九列协议组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(单据九列协议组) var ctrl = _bare_controller() var t: Dictionary = ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit", "契约探针", {"channel": "棋盘兑付", "base_value": 1, "target_rule": DamageControllerScript.TARGET_HERO, "ri": 0}) @@ -111,6 +117,8 @@ func _bare_controller(): # ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ---- func _test_status_boolean_acceptance(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:250(受理布尔面组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250(受理布尔面组) var effects = StatusContainerScript.new(db) # EC1 域闭:notify_battle_start 前授予 = false(受理面封闭) var any_row: Dictionary = {} @@ -137,6 +145,8 @@ func _test_status_boolean_acceptance(db) -> void: # 个体键直传字面量("e1"/"e2"/"e3"——拼写约定单点在 StatusContainer.owner_key_for,测试面模拟授予路径逐只授) func _test_status_individual_isolation(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:251(按个体归属组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:251(按个体归属组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -158,6 +168,8 @@ func _test_status_individual_isolation(db) -> void: func _test_status_grant_all_per_unit(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:252(授予即生效组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:252(授予即生效组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "mine_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -184,6 +196,8 @@ func _test_status_grant_all_per_unit(db) -> void: func _test_status_death_clears(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:250(战斗结束清空组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250(战斗结束清空组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -197,6 +211,8 @@ func _test_status_death_clears(db) -> void: func _test_status_merge_per_unit(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:253(只登记不去重组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:253(只登记不去重组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -219,6 +235,8 @@ func _test_status_merge_per_unit(db) -> void: # ---- B4 结算链契约(断言补齐批——①增益状态结算链):衰减逐值/战斗结束全清/满层拒绝/独立并存/首授快照 ---- func _test_status_decay_and_clear(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:21(先兑付后衰减组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:21(先兑付后衰减组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -239,6 +257,8 @@ func _test_status_decay_and_clear(db) -> void: func _test_status_stack_full_reject(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:255(满层拒绝原子且留痕组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:255(满层拒绝原子且留痕组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -256,6 +276,8 @@ func _test_status_stack_full_reject(db) -> void: func _test_status_independent_branch(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:257(合成不设上下限组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:257(合成不设上下限组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var probe := { @@ -280,6 +302,8 @@ func _test_status_independent_branch(db) -> void: func _test_status_snapshot_first_grant(db) -> void: +# oracle: design/gdd/systems/战斗循环/增益状态.md:258(快照抗重载组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:258(快照抗重载组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src_first := {"source_class": "seal_hit", "origin_cell": Vector2i(3, 4), "round_index": 1} From 085717166d2e56c8f6295a8ff4a86ad77bf565ee Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:31:03 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B04/7=C2=B7presentation+?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E6=8E=A2=E9=92=88+system/smoke/example):=202?= =?UTF-8?q?1=E5=A5=97=E4=BB=B6+11=E6=8E=A2=E9=92=88+3=E5=AE=BF=E4=B8=BB?= =?UTF-8?q?=E9=80=90=E5=87=BD=E6=95=B0/=E9=80=90=E6=AE=B5=E6=8C=82#=20orac?= =?UTF-8?q?le=E6=B3=A8=E8=AE=B0=E2=80=94=E2=80=94HUD=E6=A1=86=E6=9E=B6:260?= =?UTF-8?q?-282=E6=8B=92=E7=BB=9D=E4=B8=89=E6=BA=90/=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E8=A1=8C/=E7=8A=B6=E6=80=81=E6=A7=BD/=E5=BE=AE=E5=85=89/?= =?UTF-8?q?=E4=B8=89=E5=8C=BA=E5=87=A0=E4=BD=95/=E5=AF=B9=E6=AF=94?= =?UTF-8?q?=E5=BA=A6+camp.md:16/29/45+level-map:3/11+level-clear+node-rewa?= =?UTF-8?q?rd+=E8=AE=BE=E7=BD=AE=E4=B8=8E=E6=9C=AC=E5=9C=B0=E5=8C=96:95+?= =?UTF-8?q?=E9=94=BB=E9=80=A0=E5=B7=A5=E5=9D=8A=E7=AA=97=E5=8F=A3=E5=86=85?= =?UTF-8?q?=E5=AE=B9+battle.md:122/128/154+=E6=88=98=E6=96=97=E8=A1=A8?= =?UTF-8?q?=E7=8E=B0:148+test-conventions:30/37/84+code-conventions:41/54+?= =?UTF-8?q?architecture:168+README=E6=9C=AF=E8=AF=AD=E8=A1=A8;=20tune=5Fsi?= =?UTF-8?q?m=5Fbatch=5Fparity(=E8=B0=83=E5=8F=82=E6=B5=8B=E9=87=8F?= =?UTF-8?q?=E5=9F=9F)=E9=9B=B6=E8=A7=84=E6=A0=BC=E5=8F=A5=E4=B8=8D?= =?UTF-8?q?=E9=80=A0=E9=94=9A=E2=80=94=E2=80=94=E8=AE=B0=E8=A7=84=E6=A0=BC?= =?UTF-8?q?=E7=BC=BA=E5=8F=A3=E5=BE=85=E8=A3=81;=20=E6=9C=AC=E6=AD=A5?= =?UTF-8?q?=E5=87=AD=E6=8D=AE:=20=E6=8A=BD=E9=AA=8C4=E5=A5=97=E4=BB=B6+5?= =?UTF-8?q?=E6=8E=A2=E9=92=88+system=5Ftest+ui=5Fclick=E7=9B=B4=E8=B7=91?= =?UTF-8?q?=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/board_shot_probe.gd | 2 ++ game/probe/fx_probe.gd | 1 + game/probe/fx_shot_probe.gd | 1 + game/probe/layout_probe.gd | 2 ++ game/probe/level_map_shot_probe.gd | 1 + game/probe/level_map_viewport_probe.gd | 3 +++ game/probe/t17_defer_probe.gd | 1 + game/probe/type_glow_shot_probe.gd | 1 + game/probe/ui_click_probe.gd | 10 ++++++++-- game/probe/ui_contrast_probe.gd | 1 + game/tests/smoke_boot.gd | 3 +++ game/tests/system_test.gd | 1 + game/tests/unit/example_test.gd | 1 + game/tests/unit/presentation/battle_narrator_test.gd | 6 ++++++ .../unit/presentation/camp_scene_layout_test.gd | 2 ++ game/tests/unit/presentation/forge_workbench_test.gd | 2 ++ game/tests/unit/presentation/hud_presenter_test.gd | 4 ++++ .../unit/presentation/level_map_info_fog_test.gd | 5 +++++ game/tests/unit/presentation/modal_flow_test.gd | 1 + game/tests/unit/presentation/pacing_queue_test.gd | 1 + .../unit/presentation/player_copy_sweep_test.gd | 7 +++++++ .../presentation_encapsulation_sweep_test.gd | 6 ++++++ game/tests/unit/presentation/reward_overlay_test.gd | 3 +++ .../tests/unit/presentation/seal_mark_params_test.gd | 4 ++++ game/tests/unit/presentation/settings_store_test.gd | 4 ++++ .../tests/unit/presentation/stage_background_test.gd | 5 +++++ .../tests/unit/presentation/standee_fx_sweep_test.gd | 1 + .../unit/presentation/standee_present_values_test.gd | 2 ++ .../unit/presentation/terrain_tile_sets_test.gd | 4 ++++ .../tests/unit/presentation/theme_projection_test.gd | 12 ++++++++++++ game/tests/unit/presentation/type_glow_test.gd | 1 + game/tests/unit/presentation/ui_font_asset_test.gd | 1 + 32 files changed, 97 insertions(+), 2 deletions(-) diff --git a/game/probe/board_shot_probe.gd b/game/probe/board_shot_probe.gd index 17bd56b0..85fa5d23 100644 --- a/game/probe/board_shot_probe.gd +++ b/game/probe/board_shot_probe.gd @@ -26,6 +26,7 @@ var _cur_settlement = null # 当关 settlement(身份回调读取面—— func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84(棋盘截图取证——渲染闸采集组) # 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空) if DisplayServer.get_name() == "headless": print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);现行事实源=tools/render_gate.sh 取证段") @@ -181,6 +182,7 @@ func _ready() -> void: # 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255 #(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。 func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void: +# oracle: design/engineering/test-conventions.md:84(封印印记像素取证组) var ms = settlement.minesweeper var seal_cell := Vector2i(-9, -9) var ref_cell := Vector2i(-9, -9) diff --git a/game/probe/fx_probe.gd b/game/probe/fx_probe.gd index b26f509b..dd1416e5 100644 --- a/game/probe/fx_probe.gd +++ b/game/probe/fx_probe.gd @@ -8,6 +8,7 @@ extends Node func _ready() -> void: +# oracle: design/gdd/systems/战斗循环/战斗表现.md:148(结算演出——挖掘期零打扰组) await get_tree().process_frame var problems: Array = [] diff --git a/game/probe/fx_shot_probe.gd b/game/probe/fx_shot_probe.gd index 14075ee3..d2f2375b 100644 --- a/game/probe/fx_shot_probe.gd +++ b/game/probe/fx_shot_probe.gd @@ -21,6 +21,7 @@ var _problems: Array = [] func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84(特效截图取证——渲染闸采集组) var args := OS.get_cmdline_user_args() out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-fx" DirAccess.make_dir_recursive_absolute(out_dir) diff --git a/game/probe/layout_probe.gd b/game/probe/layout_probe.gd index cdeebcdc..4fd16f21 100644 --- a/game/probe/layout_probe.gd +++ b/game/probe/layout_probe.gd @@ -19,6 +19,7 @@ var _est_reads: Dictionary = {} # 逐档 13/14 板推定读数留痕(label func _ready() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(战斗屏三区几何与棋盘格边长——分辨率档位组) await get_tree().process_frame var main: Node = load("res://presentation/main.tscn").instantiate() add_child(main) @@ -58,6 +59,7 @@ func _ready() -> void: # 一份不变量断言,逐档代入——代码量与档数无关 func _check_one(label: String, main: Node) -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(单档几何断言组) var vr: Rect2 = get_viewport().get_visible_rect() if vr.size != Vector2(VIEWPORTS[0]["size"]) and vr.size.y < 1.0: _problems.append("[%s] 逻辑视口退化 %s" % [label, vr.size]) diff --git a/game/probe/level_map_shot_probe.gd b/game/probe/level_map_shot_probe.gd index 0f920158..0b34ff25 100644 --- a/game/probe/level_map_shot_probe.gd +++ b/game/probe/level_map_shot_probe.gd @@ -11,6 +11,7 @@ extends Node const SEED_L3 := 910212 # 深图固定种子(与 level_map_viewport_probe 同源——图确定性承载) func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84(关卡画面截图取证——载入守卫组) var args := OS.get_cmdline_user_args() var out_dir := args[0] if args.size() > 0 else "user://" await get_tree().process_frame diff --git a/game/probe/level_map_viewport_probe.gd b/game/probe/level_map_viewport_probe.gd index 6c1ede3a..0b6448f0 100644 --- a/game/probe/level_map_viewport_probe.gd +++ b/game/probe/level_map_viewport_probe.gd @@ -28,6 +28,7 @@ func _check(desc: String, ok: bool) -> void: func _ready() -> void: +# oracle: design/gdd/ux/level-map.md:3(关卡画面视口跟随——卷轴视口组) await get_tree().process_frame await get_tree().process_frame var host := Control.new() @@ -60,6 +61,7 @@ func _ready() -> void: ## R1 浅图整幅居中无裁切+画布入视口件承载(画布=视口首子件——setup 注入序) func _probe_shallow_no_crop(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3(浅层不裁切组) _runner = _make_runner_entry(1, SEED_L1) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame @@ -78,6 +80,7 @@ func _probe_shallow_no_crop(screen, vp) -> void: ## R5 前置+深图入口停点:锚点=入口组包围盒中心夹紧(spec :101——多入口并列取包围盒居中) func _probe_deep_entry_clamp(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3(深层入口钳制组) _runner = _make_runner_entry(3, SEED_L3) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame diff --git a/game/probe/t17_defer_probe.gd b/game/probe/t17_defer_probe.gd index 086adf3c..3fab58f2 100644 --- a/game/probe/t17_defer_probe.gd +++ b/game/probe/t17_defer_probe.gd @@ -13,6 +13,7 @@ var _problems: Array = [] func _ready() -> void: +# oracle: design/gdd/ux/battle.md:122(明细带挖掘期收起——T17 视觉扩展时序组) await get_tree().process_frame main = load("res://presentation/main.tscn").instantiate() add_child(main) diff --git a/game/probe/type_glow_shot_probe.gd b/game/probe/type_glow_shot_probe.gd index 73fd27fe..10f024dc 100644 --- a/game/probe/type_glow_shot_probe.gd +++ b/game/probe/type_glow_shot_probe.gd @@ -30,6 +30,7 @@ var _hero_pos := Vector2i.ZERO func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84(微光截图取证——渲染闸采集组) if DisplayServer.get_name() == "headless": print("[type_glow_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空)") get_tree().quit(1) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index cae32cfd..823383b3 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -54,6 +54,7 @@ func _ready() -> void: if main.ui.modals.menu.debug_overlay.visible: _problems.append("调试浮窗 boot 自动开(菜单优先失守)") # ---- 营地购买合成点击路径(选档进档开层→购买→关闭全走真实按钮信号路径; + # oracle: design/gdd/systems/跨局循环/天赋树.md:198(营地购买段——单币购买只扣钥石组) # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json);陈旧档段首清理)---- for p0 in ["user://smoke_click_probe_reset.json", "user://smoke_click_probe_reset.json.tmp"]: if FileAccess.file_exists(p0): @@ -191,6 +192,7 @@ func _ready() -> void: if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible: _problems.append("营地关闭后未恢复开始层〔句柄子容器批 V1〕") # ---- 选档界面行为走查(迭代 33 卡③):sl5 空槽新档链/sl6 有档槽进档链/sl7 删档二次确认双路径 + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(选档走查段——激活态建立组) # /sl8 消费方分派单口/sl12 动线序不回闪。开层=函数直调(「进入存档」钮归卡② mn3——中间态在案); # 隔离=attach_slot_template 注入命名空间,夹具按槽直写(卡① 注入口)。---- var csel_tpl := "user://smoke_click_probe_savesel_slot_%d.json" @@ -294,6 +296,7 @@ func _ready() -> void: _problems.append("[savesel] 关闭后层未隐藏或开始层未恢复(模态栈单口失守)") # ---- 设置面段(隔离 store 换注禁碰真实 user://settings.cfg;真实按钮信号路径)---- + # oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置面段——最小设置面组) for p0 in ["user://smoke_click_probe_settings.cfg"]: if FileAccess.file_exists(p0): DirAccess.remove_absolute(p0) @@ -360,6 +363,7 @@ func _ready() -> void: if ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "退出游戏", "prefix") != null: _problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)") # ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写, + # oracle: design/gdd/ux/camp.md:45(生涯页段——同壳同形态组) # 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)---- # 生涯页驱动=函数直调中间态(卡② mn10——careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径) if true: @@ -604,7 +608,7 @@ func _ready() -> void: _run_modal_probes_once() _play_one_action() # ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提 - # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 + # oracle: design/gdd/ux/level-map.md:11(主循环选路段——入口候选组) # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 # level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。 if not _done and not main.ui.modals.run.end_overlay.is_open(): var close_guard := 8000 @@ -649,7 +653,7 @@ func _ready() -> void: _play_one_action() # 排播期/Waiting 等待态=自然流逝(演播秒级、锁随队列空解除) # T17 适配:战败或放弃二途皆达终局面(放弃=预算耗尽收口路径——见上),断点段前提不变。 - await get_tree().process_frame + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(断点段——断点域与续玩组) await get_tree().process_frame var bp_file := "user://smoke_click_probe_reset.json" var iso_chk = load("res://core/save_manager.gd").new(bp_file) iso_chk.load_from_disk() @@ -680,6 +684,7 @@ func _ready() -> void: # ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。 # ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)---- + # oracle: design/gdd/ux/camp.md:16(大门段——点大门先出确认组) # 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。 # 无断点=两出口(是/否);有断点=三出口(set_breakpoint 注入选路停点形——同下方开场行③先例)。 # 判据绑可观测提交面:点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 恒= @@ -1046,6 +1051,7 @@ func _click_stop() -> void: # ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ---- +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236(锁期/重放段——拒绝与锁组) # 锁期断言(排播队列活跃=锁面):注入封印击 → 直落被冻结不受理(封印未置)+ `_locked_click` # 缓存命中且为本注入(「最后一次」语义——注入帧内无并发点击)。 # 重放断言(演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道):缓存清空 + diff --git a/game/probe/ui_contrast_probe.gd b/game/probe/ui_contrast_probe.gd index 98de808f..860c9d37 100644 --- a/game/probe/ui_contrast_probe.gd +++ b/game/probe/ui_contrast_probe.gd @@ -88,6 +88,7 @@ func _ratio(a: Color, b: Color) -> float: func _ready() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(UI 文字对比度机检 ≥4.5:1 无区域例外——对比度组) var problems: Array = [] var pairs := _pairs() for p in pairs: diff --git a/game/tests/smoke_boot.gd b/game/tests/smoke_boot.gd index 2220633a..eed5e76e 100644 --- a/game/tests/smoke_boot.gd +++ b/game/tests/smoke_boot.gd @@ -16,6 +16,7 @@ var _checks: int = 0 func check(cond: bool, label: String) -> void: +# oracle: game/tests/README.md:366(冒烟宿主判据通道——存活检查组) _checks += 1 if cond: print(" [PASS] " + label) @@ -34,6 +35,7 @@ func _ready() -> void: # 开机前置面:autoload 注册与资源可加载(挂主屏之前先查,失败时主屏断言整段跳过) func _check_autoloads() -> void: +# oracle: game/tests/README.md:366(autoload 在位组) for autoload_name in ["ConfigDB", "RngService", "SaveManager", "MetaProgression"]: var node: Node = get_node_or_null(NodePath("/root/" + autoload_name)) check(node != null, "autoload %s 在位" % autoload_name) @@ -62,6 +64,7 @@ func _boot_main_scene() -> Node: # 主屏存活面:组合根装配句柄在位(main.X 一级句柄契约,探针挂载假设的启动侧证明) func _check_main_scene(main: Node) -> void: +# oracle: game/tests/README.md:366(主屏可达组) check(main != null and main.is_inside_tree(), "主屏场景已实例化并在树") if main == null: return diff --git a/game/tests/system_test.gd b/game/tests/system_test.gd index 0f88873f..b98c626a 100644 --- a/game/tests/system_test.gd +++ b/game/tests/system_test.gd @@ -409,6 +409,7 @@ func _face_liveness(name: String, v, want: String) -> String: #(level_started→choices_presented 同步到达,无可观测中间态)——合并为 V3b 一处断言。 # (自 game/smoke/smoke_all.gd 剪切迁入——label 与 cond 逐字原样,断言迁移批 B G1) func _smoke_lifecycle_matrix() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:284(生命周期矩阵——三关序列组) print("--- 迭代28 T3 生命周期存活性矩阵(V3:九阶段×五引用面)---") var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) diff --git a/game/tests/unit/example_test.gd b/game/tests/unit/example_test.gd index e10073a1..e5412e1a 100644 --- a/game/tests/unit/example_test.gd +++ b/game/tests/unit/example_test.gd @@ -10,6 +10,7 @@ extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:check/汇总退出样板单点 func _init() -> void: +# oracle: design/engineering/test-conventions.md:37(套件协议模板——执行载体与退出码范例组) Kit.check("headless SceneTree 协议可用(quit(code) 通道)", _test_headless_protocol()) Kit.check("命名规范可执行:test_[scenario]_[expected]", _test_naming_convention_followed()) quit(Kit.finish("example_test")) diff --git a/game/tests/unit/presentation/battle_narrator_test.gd b/game/tests/unit/presentation/battle_narrator_test.gd index 09255c20..a5153d9c 100644 --- a/game/tests/unit/presentation/battle_narrator_test.gd +++ b/game/tests/unit/presentation/battle_narrator_test.gd @@ -18,6 +18,7 @@ class _BossRunner: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(战斗叙述宿主——装载组) _test_numeric_lines_carry_values() _test_end_text_differs_by_result() _test_phase_text_nonempty() @@ -28,6 +29,7 @@ func _init() -> void: func _test_numeric_lines_carry_values() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(结算行数值带入组) var rest: String = NarratorScript.rest_line(12, 30, 40) Kit.check("休整叙述带入回复量与当前血量(实际「%s」)" % rest, rest.contains("12") and rest.contains("30") and rest.contains("40")) @@ -38,6 +40,7 @@ func _test_numeric_lines_carry_values() -> void: func _test_end_text_differs_by_result() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(胜负互异文案组) # 关卡收束(level_end=true)恒为「关卡完成!」——单场胜负文案只在非收束态出现 var level: String = NarratorScript.battle_end_text("lose", true) Kit.check("关卡收束态恒为「关卡完成!」(实际「%s」)" % level, level == "关卡完成!") @@ -50,6 +53,7 @@ func _test_end_text_differs_by_result() -> void: func _test_phase_text_nonempty() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(阶段行非空组) var t: String = NarratorScript.phase_text(1, "", false, false, "") Kit.check("相位叙述非空(实际「%s」)" % t, t != "") var boss: String = NarratorScript.phase_text(2, "", false, true, "灰矮人石君王") @@ -59,6 +63,7 @@ func _test_phase_text_nonempty() -> void: # 叙述寻址按行类取 Boss 行集(S 清单 S15 换代):Boss 节点名必须来自 (关卡, boss, 带) 行集, # 不能落到同深度的战斗行集——期望名由表行集首行读出,不写死字面量。 func _test_boss_name_uses_boss_rowset() -> void: +# oracle: design/gdd/systems/战斗循环/敌人系统.md:295(Boss 名读行集组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "叙述寻址用例:数值表加载成功") @@ -87,6 +92,7 @@ func _test_boss_name_uses_boss_rowset() -> void: # 关卡完成停点文案族措辞钉子(口径换代批):回血行/满血合法态/渠道句/摘要关数行—— # 旧措辞退役后玩家可见面由本组断言钉在新措辞上(撤销证明:回退 battle_narrator 对应行→断言红→复原复绿) func _test_level_clear_wording() -> void: +# oracle: design/gdd/ux/level-clear.md:11(关卡结算屏文案组) var healed: String = NarratorScript.curio_pick_header({"level_index": 1, "heal_gained": 5, "hp_before": 10, "hp_after": 15}) Kit.check("关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" % healed, healed.contains("关卡完成回血 +5(生命 10 → 15)")) diff --git a/game/tests/unit/presentation/camp_scene_layout_test.gd b/game/tests/unit/presentation/camp_scene_layout_test.gd index 8605dc48..9da306de 100644 --- a/game/tests/unit/presentation/camp_scene_layout_test.gd +++ b/game/tests/unit/presentation/camp_scene_layout_test.gd @@ -10,10 +10,12 @@ const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest" func _initialize() -> void: +# oracle: design/gdd/ux/camp.md:29(营地场景布局数据件——纵深与地平线组) call_deferred("_test_layout_hotspot_geometry") func _test_layout_hotspot_geometry() -> void: +# oracle: design/gdd/ux/camp.md:16(七物件命中区几何红线组) var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres") Kit.check("V14 布局表加载且 schema 类在位〔营地图批 V14〕", table is CampSceneLayoutTable and table != null) if table == null: diff --git a/game/tests/unit/presentation/forge_workbench_test.gd b/game/tests/unit/presentation/forge_workbench_test.gd index fc05d97b..a4f94893 100644 --- a/game/tests/unit/presentation/forge_workbench_test.gd +++ b/game/tests/unit/presentation/forge_workbench_test.gd @@ -10,6 +10,7 @@ const _TEST_SAVE := "user://test_forge_workbench.json" func _initialize() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219(工作间两区六路线——装载组) call_deferred("_run") @@ -21,6 +22,7 @@ func _row_by_id(ov) -> Dictionary: func _run() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219(六路线 UI 直验组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) if not load_ok: diff --git a/game/tests/unit/presentation/hud_presenter_test.gd b/game/tests/unit/presentation/hud_presenter_test.gd index 7912de12..5aaba4c7 100644 --- a/game/tests/unit/presentation/hud_presenter_test.gd +++ b/game/tests/unit/presentation/hud_presenter_test.gd @@ -9,6 +9,7 @@ const HudScript := preload("res://presentation/hud_presenter.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(HUD 投影宿主——装载组) _test_accepted_text() _test_reject_text_by_reason() _test_unknown_reason_fallback() @@ -16,11 +17,13 @@ func _init() -> void: func _test_accepted_text() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(受理反馈呈现组) var t: String = HudScript.outcome_text({"accepted": true}) Kit.check("受理态文案固定为「执行完成。」(实际「%s」)" % t, t == "执行完成。") func _test_reject_text_by_reason() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(拒绝三源可辨文案组) var cases := { "not_adjacent": "不能执行:目标与小人不相邻", "target_revealed": "不能执行:目标已开", @@ -39,6 +42,7 @@ func _test_reject_text_by_reason() -> void: func _test_unknown_reason_fallback() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(未知兜底文案组) var t: String = HudScript.outcome_text({"accepted": false, "reject_reason": "brand_new_reason"}) Kit.check("未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" % t, t == "动作被拒。") var sub: String = HudScript.outcome_text({ diff --git a/game/tests/unit/presentation/level_map_info_fog_test.gd b/game/tests/unit/presentation/level_map_info_fog_test.gd index 4c4163fa..b0f42d62 100644 --- a/game/tests/unit/presentation/level_map_info_fog_test.gd +++ b/game/tests/unit/presentation/level_map_info_fog_test.gd @@ -11,10 +11,12 @@ const ScreenScript := preload("res://presentation/views/level_map_screen.gd") func _initialize() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(信息迷雾呈现宿主——装载组) call_deferred("_run") func _run() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(迷雾显隐宿主组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "迷雾呈现用例:数值表加载成功") @@ -46,6 +48,7 @@ func _runner(db, nodes: Array, candidates: Array, n: int = 2): # ---- ① 范围外只见结构:雾下节点类型图标与文字均不绘制〔fv2〕---- func _test_fog_hides_type() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(雾下类型隐藏组) var node := {"id": 3, "type": "battle", "depth": 3, "next": []} var shown: Dictionary = ScreenScript.node_display(node, 2, [2], false, false) Kit.check("可见集内节点:图标/类型文字均绘制〔fv2〕", @@ -59,6 +62,7 @@ func _test_fog_hides_type() -> void: # ---- ② Boss/终点不入雾+多终点标记〔fv3〕---- func _test_boss_exempt_and_multi_boss() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(Boss 描边豁免与多 Boss 组) var type_vis: Array = [1, 7, 9] # 双 Boss 图:7/9 均在可见集 var boss_a: Dictionary = ScreenScript.node_display({"id": 7, "type": "boss", "depth": 7, "next": []}, 7, type_vis, false, false) var boss_b: Dictionary = ScreenScript.node_display({"id": 9, "type": "boss", "depth": 9, "next": []}, 9, type_vis, false, false) @@ -76,6 +80,7 @@ func _test_boss_exempt_and_multi_boss() -> void: # ---- ③ 节点内详情图上从不显示〔fv5〕---- func _test_details_never_shown() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(详情零显示组) var plain := {"id": 3, "type": "battle", "depth": 3, "next": [4]} var loaded := {"id": 3, "type": "battle", "depth": 3, "next": [4], "enemy_id": "goblin_secret", "reward_id": "reward_secret"} var a: Dictionary = ScreenScript.node_display(plain, 3, [3], false, true) diff --git a/game/tests/unit/presentation/modal_flow_test.gd b/game/tests/unit/presentation/modal_flow_test.gd index 97177d67..892eaa4e 100644 --- a/game/tests/unit/presentation/modal_flow_test.gd +++ b/game/tests/unit/presentation/modal_flow_test.gd @@ -11,6 +11,7 @@ const ModalStackScript := preload("res://presentation/modal_stack.gd") func _init() -> void: +# oracle: design/engineering/code-conventions.md:54(模态编排纯逻辑——层级与输入锁组) # 哑层与桩 Callable(纯逻辑类零场景前提的证明面) var start := Control.new() var inter := Control.new() diff --git a/game/tests/unit/presentation/pacing_queue_test.gd b/game/tests/unit/presentation/pacing_queue_test.gd index 2a7bf058..5992ea18 100644 --- a/game/tests/unit/presentation/pacing_queue_test.gd +++ b/game/tests/unit/presentation/pacing_queue_test.gd @@ -42,6 +42,7 @@ func check(cond: bool, msg: String) -> void: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(排播节奏——排空零分叉/零丢行/延迟双态组) var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) await process_frame diff --git a/game/tests/unit/presentation/player_copy_sweep_test.gd b/game/tests/unit/presentation/player_copy_sweep_test.gd index b0b5db89..0da52ecc 100644 --- a/game/tests/unit/presentation/player_copy_sweep_test.gd +++ b/game/tests/unit/presentation/player_copy_sweep_test.gd @@ -23,6 +23,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(玩家面文案清扫宿主——装载组) _db = ConfigDBScript.new() var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: @@ -42,6 +43,7 @@ func _init() -> void: # ① C 区两段头(u1/u2——直建 hero 面板,adventure_feats_test ⑦c 族同构) func _sweep_hero_panel() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(英雄面板文案组) var panel = load("res://presentation/views/hero_info_panel_view.gd").new(_db.presentation.battle_layout) var feat_head := _first_label(panel._feat_section) var curio_head := _first_label(panel._curio_section) @@ -53,6 +55,7 @@ func _sweep_hero_panel() -> void: # ②③ 奖励节点头部+专长卡兜底(u3/u11——静态直调) func _sweep_narrator() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(叙述文案组) var narrator = load("res://presentation/battle_narrator.gd") var head := String(narrator.node_curio_header()) Kit.check("奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕", head.contains("冒险牌库")) @@ -65,6 +68,7 @@ func _sweep_narrator() -> void: # ④ 图例封印行(u4——直建 LegendOverlay 读 ENTRIES 常量) func _sweep_legend() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(图例文案组) load("res://presentation/views/legend_overlay.gd").new() # 直建(_init(rap_cost) 现签名) var seal_row: Array = [] for e in load("res://presentation/views/legend_overlay.gd").ENTRIES: @@ -80,6 +84,7 @@ func _sweep_legend() -> void: # ⑤⑥ 来源名双源(u5/u6——状态槽消费半+表值 ConfigDB 真装载+battle_log 行消费半) func _sweep_source_names() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(来源名读表组) var row_view = load("res://presentation/views/status_slot_row.gd").new() row_view.set_effects([{"effect_id": "alert", "source_class": "mine_hit", "display_name": "危险格受击", "remaining_settlements": 2, "round_index": 1}]) @@ -105,6 +110,7 @@ func _sweep_source_names() -> void: # ⑦⑧ 已开危险格字形族(u7/u8/u9——BoardView rebuild 真实刷新路径+源 token 扫描) func _sweep_board_glyph() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(危险格字形组) var board_generator = load("res://core/board_generator.gd").new() var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db) var ae = load("res://core/action_economy.gd").new(_db) @@ -160,6 +166,7 @@ func _sweep_token_scans() -> void: # 刷新两 toast 文案零数字字面量(架构整改 R4——格式串零数字、插值位与表字段一一对应; # 静态判据+评审注记观测面局限:真机文案随表值由实机目检兜——T10 清单两项) func _sweep_refresh_toast_no_literals() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(刷新提示读表拼接——零字面量组) var main_src := FileAccess.get_file_as_string("res://presentation/main.gd") var rap_toast := "" var cap_toast := "" diff --git a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd index bba15d00..461752d3 100644 --- a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd +++ b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd @@ -33,6 +33,7 @@ const A1_FILES := { func _init() -> void: +# oracle: design/engineering/code-conventions.md:41(presentation 封装——四族零命中扫描组) _sweep_a1_data_classes() _sweep_a2_subparts() _sweep_b_combo_root() @@ -42,6 +43,7 @@ func _init() -> void: # 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) func _src(path: String) -> String: +# oracle: design/engineering/code-conventions.md:41(源码采样组) var src := FileAccess.get_file_as_string(path) Kit.check("扫描面非空前置:%s" % path, src.length() > 0) return src @@ -49,18 +51,21 @@ func _src(path: String) -> String: # 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位;suffix 区分族组) func _zero(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: +# oracle: design/engineering/code-conventions.md:41(零命中扫描组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 零命中〔%s〕" % [label, pattern, suffix], re.search(src) == null) # 正锚在案断言(正则命中 ≥1——新公开面/新形态在用;suffix 区分族组) func _anchor(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: +# oracle: design/engineering/code-conventions.md:41(正锚存在组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 正锚在案〔%s〕" % [label, pattern, suffix], re.search(src) != null) # ② V2 A2 拆件子件(行为归位子件公开方法、读走公开读法、身份构造交付) func _sweep_a2_subparts() -> void: +# oracle: design/engineering/code-conventions.md:41(A2 子件封装扫描组) var body := _src("res://presentation/views/standee_body.gd") if body.length() > 0: _zero("\\bst\\._|\\bmo\\._", body, "子件私取模式零命中(standee_body)", "pres封装 V2") @@ -77,6 +82,7 @@ func _sweep_a2_subparts() -> void: # ③ V3 B 组合根交接面(main+五分文件——成对单行转发梯换代为公开入口+Callable 束) func _sweep_b_combo_root() -> void: +# oracle: design/engineering/code-conventions.md:41(组合根束封装扫描组) var b_files := { "res://presentation/main.gd": false, "res://presentation/main_ui_build.gd": true, diff --git a/game/tests/unit/presentation/reward_overlay_test.gd b/game/tests/unit/presentation/reward_overlay_test.gd index 0f02d1b9..f6323e36 100644 --- a/game/tests/unit/presentation/reward_overlay_test.gd +++ b/game/tests/unit/presentation/reward_overlay_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持) func _req(desc: String, ok: bool) -> bool: +# oracle: design/gdd/ux/node-reward.md:9(奖励覆盖层直验——需求包装组) Kit.check(desc, ok) if not ok: quit(1) @@ -16,9 +17,11 @@ func _req(desc: String, ok: bool) -> bool: return true func _initialize() -> void: +# oracle: design/gdd/ux/node-reward.md:9(奖励节点装载组) call_deferred("_run") func _run() -> void: +# oracle: design/gdd/ux/node-reward.md:9(奖励三选一呈现组) var ov = load("res://presentation/views/curio_pick_overlay.gd").new() root.add_child(ov) var picked := [] diff --git a/game/tests/unit/presentation/seal_mark_params_test.gd b/game/tests/unit/presentation/seal_mark_params_test.gd index 2b72e8b5..f13d220b 100644 --- a/game/tests/unit/presentation/seal_mark_params_test.gd +++ b/game/tests/unit/presentation/seal_mark_params_test.gd @@ -10,6 +10,7 @@ const CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd") func _init() -> void: +# oracle: design/gdd/ux/battle.md:128(封印印记参数宿主——装载组) _test_param_values() _test_shader_uniforms() _test_fog_gate() @@ -18,6 +19,7 @@ func _init() -> void: # R1–R3/R7 前置:五常量取值逐项相等(art-bible §8.2 定值) func _test_param_values() -> void: +# oracle: design/gdd/ux/battle.md:128(环径与法阵定值组) Kit.check("SEAL_RING_FRAC = 0.82〔seal批 V1〕", UiKit.SEAL_RING_FRAC == 0.82) Kit.check("SEAL_WALL_FRAC = 0.11〔seal批 V2〕", UiKit.SEAL_WALL_FRAC == 0.11) Kit.check("SEAL_ARM_FRAC = 0.156〔seal批 V2〕", UiKit.SEAL_ARM_FRAC == 0.156) @@ -27,6 +29,7 @@ func _test_param_values() -> void: # R1–R3/R7:着色器存在+七个 uniform 名在文(参数注入面契约) func _test_shader_uniforms() -> void: +# oracle: design/gdd/ux/battle.md:128(着色器参数化绘制组) var src := FileAccess.get_file_as_string(SEAL_SHADER) Kit.check("着色器文件存在且非空:%s〔seal批 V1〕" % SEAL_SHADER, src.length() > 0) if src.length() == 0: @@ -40,6 +43,7 @@ func _test_shader_uniforms() -> void: # R4 迷雾门控:seal_on × fog_state → 封印子件可见性(内部字段直查=受控例外——子件可见性无公开可观测面, # 卡面 R4 断言即 prescribe 子件 visible 直查;对照表该行注记理由) func _test_fog_gate() -> void: +# oracle: design/gdd/ux/battle.md:128(迷雾联动门控组) var u: Node2D = CELL_UNIT.new() u.seal_on = true Kit.check("seal_on 即挂封印子件〔seal批 V4〕", u._seal_layer != null) diff --git a/game/tests/unit/presentation/settings_store_test.gd b/game/tests/unit/presentation/settings_store_test.gd index 91a7e4a0..4a4710f5 100644 --- a/game/tests/unit/presentation/settings_store_test.gd +++ b/game/tests/unit/presentation/settings_store_test.gd @@ -12,6 +12,7 @@ const ISO_PATH := "user://settings_store_unit_test.cfg" # 隔离注路径( func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置存储宿主——装载组) _test_settings_roundtrip() _test_settings_default() _test_settings_apply_chain() @@ -26,6 +27,7 @@ func _cleanup() -> void: # ---- 持久化回读(写 true→存→新实例回读 true;节/键 motion/reduced)---- func _test_settings_roundtrip() -> void: +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置写读往返组) var w = StoreScript.new(ISO_PATH) w.reduced_motion = true Kit.check("前置:save_settings 落盘成功", w.save_settings()) @@ -42,6 +44,7 @@ func _test_settings_roundtrip() -> void: # ---- 缺省值(无文件缺省 false)---- func _test_settings_default() -> void: +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(出厂默认组) var absent_path := "user://settings_store_unit_absent_%d.cfg" % (Time.get_ticks_msec()) DirAccess.remove_absolute(ProjectSettings.globalize_path(absent_path)) # 确保无文件 var d = StoreScript.new(absent_path) @@ -52,6 +55,7 @@ func _test_settings_default() -> void: # ---- 引擎档应用链(reduced→apply()→UiKit.motion_scale==0.5,false→1.0;测毕还原静态初值)---- func _test_settings_apply_chain() -> void: +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(应用链组) var saved_scale: float = UiKit.motion_scale # 静态初值留底——测毕还原 var a = StoreScript.new(ISO_PATH) a.reduced_motion = true diff --git a/game/tests/unit/presentation/stage_background_test.gd b/game/tests/unit/presentation/stage_background_test.gd index cf14e387..4958b7c1 100644 --- a/game/tests/unit/presentation/stage_background_test.gd +++ b/game/tests/unit/presentation/stage_background_test.gd @@ -26,10 +26,12 @@ class AllMissingStage extends BattleStageView: func _initialize() -> void: +# oracle: design/gdd/ux/battle.md:122(舞台背景宿主——装载组) call_deferred("_run") func _run() -> void: +# oracle: design/gdd/ux/battle.md:122(B 区背景宿主组) _test_layout_ground_field() await _test_background_selection() await _test_background_fallback() @@ -40,6 +42,7 @@ func _run() -> void: # ---- V1 ground_h 字段与校验(表值/默认表 validate 空/三态拒绝族) ---- func _test_layout_ground_field() -> void: +# oracle: design/gdd/ux/battle.md:122(连续地面层组) var layout = load("res://data/tables/battle_layout.tres") Kit.check("布局表 ground_h = 100 且默认表 validate 为空〔舞台背景 V1〕", int(layout.ground_h) == 100 and layout.validate().is_empty()) @@ -72,6 +75,7 @@ func _make_stage() -> Node: # ---- V2 背景按 board_type 选图(直建默认 dungeon + 三值路径逐值对应 + 层序在世界之下) ---- func _test_background_selection() -> void: +# oracle: design/gdd/ux/battle.md:122(三关变体选图组) var layout = load("res://data/tables/battle_layout.tres") var stage: Node = _make_stage() await process_frame @@ -100,6 +104,7 @@ func _test_background_selection() -> void: # ---- V3 缺图回退(目标缺→dungeon;全缺→平色兜底不崩不刷产品日志) ---- func _test_background_fallback() -> void: +# oracle: design/gdd/ux/battle.md:122(缺位回退组) var s1: Node = MissingUnderdarkStage.new(null, load("res://data/tables/battle_layout.tres")) root.add_child(s1) await process_frame diff --git a/game/tests/unit/presentation/standee_fx_sweep_test.gd b/game/tests/unit/presentation/standee_fx_sweep_test.gd index 9c372832..ad5a9cd3 100644 --- a/game/tests/unit/presentation/standee_fx_sweep_test.gd +++ b/game/tests/unit/presentation/standee_fx_sweep_test.gd @@ -69,6 +69,7 @@ class FakeDB: func _init() -> void: +# oracle: design/gdd/ux/battle.md:154(站桩呈现域缺陷扫掠组) UiKit.motion_scale = 1.0 # ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)---- diff --git a/game/tests/unit/presentation/standee_present_values_test.gd b/game/tests/unit/presentation/standee_present_values_test.gd index 22260714..35e1e589 100644 --- a/game/tests/unit/presentation/standee_present_values_test.gd +++ b/game/tests/unit/presentation/standee_present_values_test.gd @@ -31,6 +31,7 @@ func _read_source(rel_path: String) -> String: func _init() -> void: +# oracle: design/gdd/ux/battle.md:154(站桩呈现定值与素材绑定组) # ---- V5 血条填充 = 危险红 #A8321F(§4.2 语义物件显式落法) ---- Kit.check("血条填充常量 = 危险红 #A8321F〔样张批 V5〕", UiKit.HP_BAR_FILL.to_html(false) == "a8321f") @@ -136,6 +137,7 @@ func _enemy_tex_path(key: String) -> String: func _check_display_tier() -> void: +# oracle: design/gdd/ux/battle.md:154(显示档与分辨率绑定组) # V13/V15 九键逐一:真实入口 StandeeBody → set_data(enemy_id) → set_portrait_texture(真纹理) → portrait_probe for key: String in _DISPLAY_SPEC: var spec: Array = _DISPLAY_SPEC[key] diff --git a/game/tests/unit/presentation/terrain_tile_sets_test.gd b/game/tests/unit/presentation/terrain_tile_sets_test.gd index a7f295e4..706c7155 100644 --- a/game/tests/unit/presentation/terrain_tile_sets_test.gd +++ b/game/tests/unit/presentation/terrain_tile_sets_test.gd @@ -13,6 +13,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(地形瓦片套宿主——装载组) var board_dir := ProjectSettings.globalize_path("res://assets/art/board/") var probe_path := board_dir.path_join("terrain_wall_center_dungeon.png") if not FileAccess.file_exists(probe_path): @@ -41,6 +42,7 @@ func _init() -> void: # 套完整性:词表每个 board_type 的 16 墙 + 5 地面变体全部在盘(缺位 → BoardView 整批回落 dungeon 的 # 缺位回退断言由 ui_input_probe 覆盖;本断言管「入包完整」面) func _test_set_completeness(board_dir: String, types: Array) -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(瓦片套完整性组) var wall_names: Array = TilePaletteScript.TILE_WALL_NAMES var variants: int = int(TilePaletteScript.TILE_FLOOR_VARIANTS) for bt in types: @@ -58,6 +60,7 @@ func _test_set_completeness(board_dir: String, types: Array) -> void: # 色相域抽检:wall_center 上半幅(墙顶面)与 floor_1 全幅的均值色相/明度 func _test_hue_domains(board_dir: String) -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 地形瓦片色相域(色相域组) var bands := { "dungeon": [0.02, 0.16], # 暖石棕 ≈15°–58° "underdark": [0.50, 0.72], # 灰白蓝紫 ≈180°–260° @@ -121,6 +124,7 @@ func _load_stats(board_dir: String, fname: String, top_half: bool) -> Dictionary # (双峰纹理直画不可救——渲染帧取证深墨隐形实证,牌上深墨 12:1);③直画模式的双门槛值照抄 # TilePalette 判据常量(防判据漂移) func _test_digit_ink_modes(board_dir: String, types: Array) -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(数字墨色模式组) var expect_plate := {"dungeon": false, "underdark": true, "deep_underdark": true} var variants: int = int(TilePaletteScript.TILE_FLOOR_VARIANTS) for bt in types: diff --git a/game/tests/unit/presentation/theme_projection_test.gd b/game/tests/unit/presentation/theme_projection_test.gd index 7c46c3c2..d18f3b48 100644 --- a/game/tests/unit/presentation/theme_projection_test.gd +++ b/game/tests/unit/presentation/theme_projection_test.gd @@ -30,6 +30,7 @@ var _rng func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(呈现投影与主题宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2–G5 同款:表损坏的红落在具体断言上) var load_ok: bool = _db.load_tables("res://data/tables", true) @@ -180,6 +181,7 @@ func _meta_new_save(path: String): # ---- 未开格类型微光(作用集恒等/九型归组封闭/零玩法写入)---- func _smoke_type_glow() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(未开格类型微光数据面组) print("--- 未开格类型微光(作用集恒等/九型归组封闭/零玩法写入)---") var board_generator = load("res://core/board_generator.gd").new() var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db) @@ -381,6 +383,7 @@ func _smoke_type_glow() -> void: # _format_row Invalid access 使结算段中途夭折、界面卡死。行协议变更必须同步呈现层〔战斗结算 §行协议〕, # 本段把现行行形态逐型喂进真实视图:拆行残留与行字段错位在此转红)---- func _smoke_battle_log_view() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(明细带行协议组) print("--- 战斗日志视图行格式化(行协议 9 类呈现层同步闸)---") var view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout) # 敌方行动伤害行(现行形态:baseline 在场、attach 已出协议——修复前此行即 Invalid access) @@ -425,6 +428,7 @@ func _smoke_battle_log_view() -> void: # ---- 开场行后缀尾接协议(迭代36 wc1 静态半——level_open_line 第五参布尔直控尾接,签名零变; # 该函数此前零既有断言,尾接行为由此钉住)---- func _smoke_level_open_line() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(关卡开启行组) var NarratorScript := load("res://presentation/battle_narrator.gd") Kit.check("开场行 boosted=true 尾接「(含面板加成)」〔wc1〕", String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)")) @@ -439,6 +443,7 @@ func _smoke_level_open_line() -> void: # V1 挂载覆盖 / V2 滑轨两段 / V3 预盖件运行时证词 / V4 旧形态零残留 / # V5 回归(本冒烟全绿即回归网)/ V6 中文字体防御(挂主题后 default_font 顶掉 custom_font 也不丢字形) func _smoke_global_theme() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(全局主题组) print("--- UI 全局主题(main_theme.tres 挂载与词条断言)---") var th := ThemeDB.get_project_theme() Kit.check("V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)", th != null) @@ -543,6 +548,7 @@ func _smoke_global_theme() -> void: # ---- 按钮五态矩阵冒烟(四工厂产物逐实例 override 断言;V1/V2)---- func _smoke_button_five_states() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(按钮五态组) print("--- UI 按钮五态矩阵(四工厂五态样式盒)---") var uikit_s = load("res://presentation/views/ui_kit.gd") var states := ["normal", "hover", "pressed", "disabled", "focus"] @@ -614,6 +620,7 @@ func _smoke_button_five_states() -> void: # ---- 全局纹理过滤冒烟(工程设置 Nearest+平滑渐变件 LINEAR 白名单;V3)---- func _smoke_texture_filter() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(纹理过滤白名单组) print("--- UI 全局纹理过滤(Nearest+白名单三件)---") var v = ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter") Kit.check("V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)", @@ -663,6 +670,7 @@ func _smoke_texture_filter() -> void: # ---- <4.5:1 文字位换墨冒烟(站桩徽标两族亮档+RAP 行深石墨;V4)---- func _smoke_contrast_inks() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(对比度墨色锚组) print("--- UI 残余 <4.5:1 文字位换墨(V4 对比度锚定)---") var uikit_s = load("res://presentation/views/ui_kit.gd") var stage: Color = uikit_s.SLOT_BACKDROP # 舞台底对比锚改读合并目标(art-bible §4.1 换色合并③——阈值与判据不变) @@ -689,6 +697,7 @@ func _smoke_contrast_inks() -> void: # ---- 元素细节余②③冒烟(BCard 图标槽明度差降档+商店居中+营地四栏等高)---- func _smoke_element_details() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(元素细节源码锚组) print("--- UI 元素细节余②③(图标槽降档+布局修)---") var uikit_s = load("res://presentation/views/ui_kit.gd") # ②白图标 ≥3:1 图形对比硬约束+降档方向锚定 @@ -711,6 +720,7 @@ func _smoke_element_details() -> void: # ---- 诊断转储钮冒烟(角落钮不在玩家画面+转储入口在调试浮窗;V6)---- func _smoke_diag_button_retired() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(诊断钮退役契约组) print("--- UI 诊断转储钮退役(V6:玩家画面清零+浮窗可达)---") var main_src: String = FileAccess.get_file_as_string("res://presentation/main.gd") Kit.check("V6 主区无 build_diag_button 调用残留(旧实现同批清理)", @@ -734,6 +744,7 @@ func _smoke_diag_button_retired() -> void: # 断言归 ui_struct_probe 呈现侧,本段只管数据面;跨关解锁概览断言族随面板换代退役—— # 替换判据=sv5 零旧段+八键投影断言(实现报告 §8.3 申报))---- func _status_panel_projection() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(状态面板只读投影组) print("--- 状态面板·本局信息面板投影一致性(迭代36 卡③——八键快照手拼夹具,同构口径不依赖 runner)---") _meta_smoke_cleanup() # 套件级夹具清场(生涯页 fixture 依赖此前置——旧状态段同款保留) # 夹具(代拍板 4——任意非零可辨值,禁读 data/tables 现值、禁与属性行断言共值致误判掩盖) @@ -794,6 +805,7 @@ func _all_labels(root: Node) -> Array: # 只读性连接巡检与零动效断言归 ui_struct_probe 呈现侧,本段只管数据面+旧档载入显示 # (v5 样档升 v6——完成定义 6「计数域缺省补零、页正常显示」))---- func _career_overlay_projection() -> void: +# oracle: design/gdd/ux/camp.md:45(生涯页只读投影组) print("--- 生涯页·只读投影一致性(隔离档 smoke_meta_career/career_v5)---") var sv = _meta_new_save(_META_SMOKE_PATHS[9]) var mp = load("res://core/meta_progression.gd").new(_db, sv) diff --git a/game/tests/unit/presentation/type_glow_test.gd b/game/tests/unit/presentation/type_glow_test.gd index fdfaf69d..b4fc4bb7 100644 --- a/game/tests/unit/presentation/type_glow_test.gd +++ b/game/tests/unit/presentation/type_glow_test.gd @@ -46,6 +46,7 @@ const CONST_NAME := { func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(微光数据面宿主——装载组) _test_grouping_closed() _test_frozen_hue_face() _test_render_params() diff --git a/game/tests/unit/presentation/ui_font_asset_test.gd b/game/tests/unit/presentation/ui_font_asset_test.gd index c22c1a74..6e1d7801 100644 --- a/game/tests/unit/presentation/ui_font_asset_test.gd +++ b/game/tests/unit/presentation/ui_font_asset_test.gd @@ -11,6 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(中文字体随包与字形覆盖组) var path: String = UiKitScript.FONT_UI var f: Font = UiKitScript.load_font(path) Kit.check("随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)", f != null) From cba3d647092ce21539a177e52eab3073e197a31a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:40:50 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=9C=BA=E6=A3=80=C2=B7capability=5Faudit=20?= =?UTF-8?q?G=E9=81=93):=20=E6=96=B0=E5=A2=9E=E5=88=A4=E6=8D=AE=E5=87=BA?= =?UTF-8?q?=E5=A4=84=E6=B3=A8=E8=AE=B0=E6=9C=BA=E6=A3=80=E2=80=94=E2=80=94?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=87=BD=E6=95=B0(=E5=90=ABcheck/check=5Fqui?= =?UTF-8?q?et/=5Fproblems.append/push=5Ferror=E5=88=A4=E5=AE=9A=E8=B0=83?= =?UTF-8?q?=E7=94=A8,=E5=AE=9A=E4=B9=89=E8=A1=8C=E4=B8=8E=5Fcheck=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E9=99=A4=E5=A4=96)=E9=A1=BB=E5=B8=A6#=20oracle?= =?UTF-8?q?=E6=B3=A8=E8=AE=B0+=E6=8C=87=E9=92=88=E9=A1=BBdesign/=E5=85=A8?= =?UTF-8?q?=E5=9F=9F=E4=B8=94=E8=A1=8C=E5=8F=B7=E5=8F=AF=E6=A0=B8;=20?= =?UTF-8?q?=E8=87=AA=E6=A3=80=E5=9B=9B=E9=81=93=E6=89=A9=E4=BA=94=E9=81=93?= =?UTF-8?q?(=E6=89=AB=E6=8F=8F=E9=9D=A2=E9=9B=B6=E6=96=87=E4=BB=B6exit2);?= =?UTF-8?q?=20=E6=91=98=E8=A6=81=E8=A1=8C=E5=A2=9E=E6=B3=A8=E8=AE=B0?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=95=B0;=20=E5=90=8C=E6=89=B9=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3320+79=E8=A1=8C=E6=B3=A8=E8=AE=B0=E6=A0=BC=E5=BC=8F(:?= =?UTF-8?q?=E8=A1=8C=E5=8F=B7=E4=B8=8E=E5=88=A4=E6=8D=AE=E5=8F=A5=E9=97=B4?= =?UTF-8?q?=E8=A1=A5=E7=A9=BA=E6=A0=BC=E2=80=94=E2=80=94ORACLE=5FRE?= =?UTF-8?q?=E8=A6=81=E6=B1=82)+smoke=5Fboot=E4=B8=A4=E6=8C=87=E9=92=88?= =?UTF-8?q?=E6=94=B9=E9=94=9Aarchitecture:10(README=E4=B8=8D=E5=B1=9Edesig?= =?UTF-8?q?n/=E5=9F=9F);=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20capabil?= =?UTF-8?q?ity=5Faudit=20--strict=E7=BB=BF(401=E5=87=BD=E6=95=B0=E7=BA=A2?= =?UTF-8?q?=E9=A1=B90)+run=5Fsmoke=20PASS(15=E9=A1=B90=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=B8=89=E9=97=B8=E5=85=A8=E8=BF=87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/probe/board_shot_probe.gd | 5 +- game/probe/fx_probe.gd | 2 +- game/probe/fx_shot_probe.gd | 4 +- game/probe/layout_probe.gd | 4 +- game/probe/level_map_shot_probe.gd | 4 +- game/probe/level_map_viewport_probe.gd | 14 +++- game/probe/t17_defer_probe.gd | 2 +- game/probe/type_glow_shot_probe.gd | 2 +- game/probe/ui_click_probe.gd | 18 +++-- game/probe/ui_contrast_probe.gd | 2 +- game/probe/ui_input_probe.gd | 1 + .../integration/cross_system_flow_test.gd | 9 ++- .../integration/node_map_determinism_test.gd | 3 +- .../integration/save_rng_roundtrip_test.gd | 5 +- game/tests/smoke_boot.gd | 6 +- game/tests/system_test.gd | 8 +- .../damage_controller_test.gd | 17 +++-- .../battle_settlement/damage_math_test.gd | 10 +-- .../battle_settlement/effect_port_test.gd | 8 +- .../extended_row_violation_test.gd | 2 +- .../battle_settlement/refresh_feats_test.gd | 26 +++---- .../battle_settlement/roll_multiplier_test.gd | 11 ++- .../round_flow_skill_pool_test.gd | 20 +++-- .../battle_settlement/seal_polarity_test.gd | 32 ++++---- .../settlement_wiring_snapshot_test.gd | 2 +- .../battle_settlement/skill_unlock_test.gd | 7 +- .../battle_settlement/trigger_cond_test.gd | 2 +- .../variant_placement_test.gd | 6 +- .../tests/unit/contracts/source_guard_test.gd | 4 +- .../unit/contracts/system_contracts_test.gd | 49 +++++++------ game/tests/unit/core/adventure_feats_test.gd | 6 +- .../unit/core/auto_player_deduce_test.gd | 2 +- .../unit/core/battle_driver_drive_test.gd | 8 +- game/tests/unit/core/board_gen_reward_test.gd | 8 +- game/tests/unit/core/board_generator_test.gd | 12 +-- .../tests/unit/core/breakpoint_resume_test.gd | 2 +- .../core/config_db_cross_validator_test.gd | 8 +- .../unit/core/config_db_resolver_test.gd | 14 ++-- .../unit/core/curio_refresh_paid_test.gd | 2 +- game/tests/unit/core/effect_words_t1_test.gd | 2 +- .../unit/core/encapsulation_p2_sweep_test.gd | 12 +-- .../unit/core/encapsulation_sweep_test.gd | 20 ++--- game/tests/unit/core/feat_vouchers_test.gd | 2 +- game/tests/unit/core/hero_ledger_test.gd | 2 +- .../unit/core/level_map_type_vis_test.gd | 17 +++-- .../unit/core/level_shop_economy_test.gd | 27 +++---- .../unit/core/level_table_validate_test.gd | 8 +- .../unit/core/meta_progression_forge_test.gd | 20 ++--- .../tests/unit/core/minesweeper_rules_test.gd | 14 ++-- .../unit/core/node_map_generator_test.gd | 21 +++--- game/tests/unit/core/node_map_growth_test.gd | 8 +- .../unit/core/playtest_logger_guard_test.gd | 2 +- game/tests/unit/core/pool_case_d_test.gd | 10 +-- game/tests/unit/core/run_time_fatal_test.gd | 7 +- .../unit/core/save_boot_no_activate_test.gd | 8 +- .../unit/core/save_manager_domain_test.gd | 5 +- .../unit/core/save_slot_isolation_test.gd | 6 +- .../tests/unit/core/save_slot_summary_test.gd | 6 +- game/tests/unit/core/settle_segments_test.gd | 2 +- game/tests/unit/core/treasure_effects_test.gd | 2 +- game/tests/unit/core/treasure_items_test.gd | 2 +- .../unit/core/tree_requires_level_test.gd | 2 +- game/tests/unit/core/tree_rules_v11_test.gd | 4 +- .../data_config/table_structure_guard_test.gd | 3 + game/tests/unit/example_test.gd | 2 +- .../unit/presentation/battle_narrator_test.gd | 13 ++-- .../presentation/camp_scene_layout_test.gd | 4 +- .../unit/presentation/forge_workbench_test.gd | 4 +- .../unit/presentation/hud_presenter_test.gd | 8 +- .../presentation/level_map_info_fog_test.gd | 13 ++-- .../unit/presentation/modal_flow_test.gd | 2 +- .../unit/presentation/pacing_queue_test.gd | 4 +- .../presentation/player_copy_sweep_test.gd | 15 ++-- .../presentation_encapsulation_sweep_test.gd | 14 ++-- .../unit/presentation/reward_overlay_test.gd | 6 +- .../presentation/seal_mark_params_test.gd | 8 +- .../unit/presentation/settings_store_test.gd | 8 +- .../presentation/stage_background_test.gd | 12 +-- .../presentation/standee_fx_sweep_test.gd | 2 +- .../standee_present_values_test.gd | 4 +- .../presentation/terrain_tile_sets_test.gd | 6 +- .../presentation/theme_projection_test.gd | 24 +++--- .../tune_sim_batch_parity_test.gd | 2 + .../tests/unit/presentation/type_glow_test.gd | 7 +- .../unit/presentation/ui_font_asset_test.gd | 2 +- tools/capability_audit.py | 73 ++++++++++++++++++- 86 files changed, 469 insertions(+), 323 deletions(-) diff --git a/game/probe/board_shot_probe.gd b/game/probe/board_shot_probe.gd index 85fa5d23..76dcdade 100644 --- a/game/probe/board_shot_probe.gd +++ b/game/probe/board_shot_probe.gd @@ -26,7 +26,7 @@ var _cur_settlement = null # 当关 settlement(身份回调读取面—— func _ready() -> void: -# oracle: design/engineering/test-conventions.md:84(棋盘截图取证——渲染闸采集组) +# oracle: design/engineering/test-conventions.md:84 (棋盘截图取证——渲染闸采集组) # 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空) if DisplayServer.get_name() == "headless": print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);现行事实源=tools/render_gate.sh 取证段") @@ -182,7 +182,7 @@ func _ready() -> void: # 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255 #(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。 func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void: -# oracle: design/engineering/test-conventions.md:84(封印印记像素取证组) +# oracle: design/engineering/test-conventions.md:84 (封印印记像素取证组) var ms = settlement.minesweeper var seal_cell := Vector2i(-9, -9) var ref_cell := Vector2i(-9, -9) @@ -290,6 +290,7 @@ func _probe_identity_lookup(c: Vector2i): # 双帧逐像素比对:所有变化像素必须落在生效格的内缘带内 # (de < cell÷8——带外含砖心零侵入=禁整格染色/禁越圈);每个生效格须有实绘像素(不空转)。 func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Image, on_img: Image) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var board = view.board() if board == null: _problems.append("%s 双帧比对取板失败(view.board() 为 null)" % tag) diff --git a/game/probe/fx_probe.gd b/game/probe/fx_probe.gd index dd1416e5..5ea3d1ad 100644 --- a/game/probe/fx_probe.gd +++ b/game/probe/fx_probe.gd @@ -8,7 +8,7 @@ extends Node func _ready() -> void: -# oracle: design/gdd/systems/战斗循环/战斗表现.md:148(结算演出——挖掘期零打扰组) +# oracle: design/gdd/systems/战斗循环/战斗表现.md:148 (结算演出——挖掘期零打扰组) await get_tree().process_frame var problems: Array = [] diff --git a/game/probe/fx_shot_probe.gd b/game/probe/fx_shot_probe.gd index d2f2375b..b7e15910 100644 --- a/game/probe/fx_shot_probe.gd +++ b/game/probe/fx_shot_probe.gd @@ -21,7 +21,7 @@ var _problems: Array = [] func _ready() -> void: -# oracle: design/engineering/test-conventions.md:84(特效截图取证——渲染闸采集组) +# oracle: design/engineering/test-conventions.md:84 (特效截图取证——渲染闸采集组) var args := OS.get_cmdline_user_args() out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-fx" DirAccess.make_dir_recursive_absolute(out_dir) @@ -108,6 +108,7 @@ func _ready() -> void: func _snap(tag: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) await RenderingServer.frame_post_draw var img: Image = get_viewport().get_texture().get_image() if img == null or img.is_empty(): @@ -117,6 +118,7 @@ func _snap(tag: String) -> void: func _fail(msg: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _problems.append(msg) push_error("[fx_shot_probe] FAIL — " + msg) get_tree().quit(1) diff --git a/game/probe/layout_probe.gd b/game/probe/layout_probe.gd index 4fd16f21..cb68fd60 100644 --- a/game/probe/layout_probe.gd +++ b/game/probe/layout_probe.gd @@ -19,7 +19,7 @@ var _est_reads: Dictionary = {} # 逐档 13/14 板推定读数留痕(label func _ready() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(战斗屏三区几何与棋盘格边长——分辨率档位组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (战斗屏三区几何与棋盘格边长——分辨率档位组) await get_tree().process_frame var main: Node = load("res://presentation/main.tscn").instantiate() add_child(main) @@ -59,7 +59,7 @@ func _ready() -> void: # 一份不变量断言,逐档代入——代码量与档数无关 func _check_one(label: String, main: Node) -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(单档几何断言组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (单档几何断言组) var vr: Rect2 = get_viewport().get_visible_rect() if vr.size != Vector2(VIEWPORTS[0]["size"]) and vr.size.y < 1.0: _problems.append("[%s] 逻辑视口退化 %s" % [label, vr.size]) diff --git a/game/probe/level_map_shot_probe.gd b/game/probe/level_map_shot_probe.gd index 0b34ff25..a201979c 100644 --- a/game/probe/level_map_shot_probe.gd +++ b/game/probe/level_map_shot_probe.gd @@ -11,7 +11,7 @@ extends Node const SEED_L3 := 910212 # 深图固定种子(与 level_map_viewport_probe 同源——图确定性承载) func _ready() -> void: -# oracle: design/engineering/test-conventions.md:84(关卡画面截图取证——载入守卫组) +# oracle: design/engineering/test-conventions.md:84 (关卡画面截图取证——载入守卫组) var args := OS.get_cmdline_user_args() var out_dir := args[0] if args.size() > 0 else "user://" await get_tree().process_frame @@ -45,6 +45,7 @@ func _settle() -> void: func _make_runner(entry: bool, node_id: int): +# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(SEED_L3) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -84,6 +85,7 @@ func _deepest_with_outs(screen, runner) -> int: func _shot(out_dir: String, fname: String) -> void: +# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组) DirAccess.make_dir_recursive_absolute(out_dir) # 落盘目录自建(save_png 对缺目录静默失败——刷新链不预建) var img := get_viewport().get_texture().get_image() var err := img.save_png(out_dir.path_join(fname)) diff --git a/game/probe/level_map_viewport_probe.gd b/game/probe/level_map_viewport_probe.gd index 0b6448f0..6b9a3031 100644 --- a/game/probe/level_map_viewport_probe.gd +++ b/game/probe/level_map_viewport_probe.gd @@ -22,13 +22,14 @@ var _runner # 当前停点的单关 runner(open_choices 驱动随记—— func _check(desc: String, ok: bool) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) _checks += 1 if not ok: _problems.append(desc) func _ready() -> void: -# oracle: design/gdd/ux/level-map.md:3(关卡画面视口跟随——卷轴视口组) +# oracle: design/gdd/ux/level-map.md:3 (关卡画面视口跟随——卷轴视口组) await get_tree().process_frame await get_tree().process_frame var host := Control.new() @@ -61,7 +62,7 @@ func _ready() -> void: ## R1 浅图整幅居中无裁切+画布入视口件承载(画布=视口首子件——setup 注入序) func _probe_shallow_no_crop(screen, vp) -> void: -# oracle: design/gdd/ux/level-map.md:3(浅层不裁切组) +# oracle: design/gdd/ux/level-map.md:3 (浅层不裁切组) _runner = _make_runner_entry(1, SEED_L1) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame @@ -80,7 +81,7 @@ func _probe_shallow_no_crop(screen, vp) -> void: ## R5 前置+深图入口停点:锚点=入口组包围盒中心夹紧(spec :101——多入口并列取包围盒居中) func _probe_deep_entry_clamp(screen, vp) -> void: -# oracle: design/gdd/ux/level-map.md:3(深层入口钳制组) +# oracle: design/gdd/ux/level-map.md:3 (深层入口钳制组) _runner = _make_runner_entry(3, SEED_L3) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame @@ -174,6 +175,7 @@ func _probe_clamp_bounds_and_columns(screen, vp) -> void: ## R6 拖拽不触发选路提交;阈值另一侧完整点击仍提交(提交语义零改动) func _probe_drag_no_submit(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) # 重建到中段可居中停点(候选=停点出边——候选节点居屏中可拖可达) var stops := _centerable_stops(screen, vp) if stops.size() < 3: @@ -212,6 +214,7 @@ func _probe_drag_no_submit(screen, vp) -> void: ## R2 停点切换后当前节点居中(视口中线投影 x=640±2——视口满屏宽即屏中线) func _probe_follow_anchor(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var stops := _centerable_stops(screen, vp) if stops.size() < 3: _problems.append("R2 可居中停点不足(=%d)" % stops.size()) @@ -227,6 +230,7 @@ func _probe_follow_anchor(screen, vp) -> void: ## R7 平滑滚动有 tween 中间帧;减动效开=位置即达(画布坐标域判定) func _probe_smooth_scroll_reduced_motion(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var saved_scale: float = UiKit.motion_scale UiKit.motion_scale = 1.0 var stops := _centerable_stops(screen, vp) @@ -266,6 +270,7 @@ func _probe_smooth_scroll_reduced_motion(screen, vp) -> void: ## R8 指引件可见性与几何(深图超窗——像素级判据②③) func _probe_guidance_chrome(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var band_l: Control = vp.get_node_or_null(NodePath("FadeL")) var band_r: Control = vp.get_node_or_null(NodePath("FadeR")) var chip: Control = vp.get_node_or_null(NodePath("PanHint")) @@ -297,6 +302,7 @@ func _probe_shallow_guidance_hidden(screen, vp) -> void: ## 入口停点 runner(defer_entry——选路态挂起、候选=入口节点) func _make_runner_entry(level_index: int, seed_val: int): +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(seed_val) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -307,6 +313,7 @@ func _make_runner_entry(level_index: int, seed_val: int): ## 中段停点 runner(断点续玩重建——图由种子+参数档重生成同图,零 RNG 消耗) func _make_runner_at_stop(level_index: int, seed_val: int, node_id: int): +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(seed_val) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -381,6 +388,7 @@ func _extreme_node(pos: Dictionary, nodes: Array, first: bool) -> Dictionary: func _finish() -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) if _checks == 0: _problems.append("零断言执行(断言数下限闸——三硬闸③)") if _problems.is_empty(): diff --git a/game/probe/t17_defer_probe.gd b/game/probe/t17_defer_probe.gd index 3fab58f2..a535e93f 100644 --- a/game/probe/t17_defer_probe.gd +++ b/game/probe/t17_defer_probe.gd @@ -13,7 +13,7 @@ var _problems: Array = [] func _ready() -> void: -# oracle: design/gdd/ux/battle.md:122(明细带挖掘期收起——T17 视觉扩展时序组) +# oracle: design/gdd/ux/battle.md:122 (明细带挖掘期收起——T17 视觉扩展时序组) await get_tree().process_frame main = load("res://presentation/main.tscn").instantiate() add_child(main) diff --git a/game/probe/type_glow_shot_probe.gd b/game/probe/type_glow_shot_probe.gd index 10f024dc..0a4d3ecb 100644 --- a/game/probe/type_glow_shot_probe.gd +++ b/game/probe/type_glow_shot_probe.gd @@ -30,7 +30,7 @@ var _hero_pos := Vector2i.ZERO func _ready() -> void: -# oracle: design/engineering/test-conventions.md:84(微光截图取证——渲染闸采集组) +# oracle: design/engineering/test-conventions.md:84 (微光截图取证——渲染闸采集组) if DisplayServer.get_name() == "headless": print("[type_glow_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空)") get_tree().quit(1) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index 823383b3..b61804bf 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -54,7 +54,7 @@ func _ready() -> void: if main.ui.modals.menu.debug_overlay.visible: _problems.append("调试浮窗 boot 自动开(菜单优先失守)") # ---- 营地购买合成点击路径(选档进档开层→购买→关闭全走真实按钮信号路径; - # oracle: design/gdd/systems/跨局循环/天赋树.md:198(营地购买段——单币购买只扣钥石组) + # oracle: design/gdd/systems/跨局循环/天赋树.md:198 (营地购买段——单币购买只扣钥石组) # 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json);陈旧档段首清理)---- for p0 in ["user://smoke_click_probe_reset.json", "user://smoke_click_probe_reset.json.tmp"]: if FileAccess.file_exists(p0): @@ -192,7 +192,7 @@ func _ready() -> void: if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible: _problems.append("营地关闭后未恢复开始层〔句柄子容器批 V1〕") # ---- 选档界面行为走查(迭代 33 卡③):sl5 空槽新档链/sl6 有档槽进档链/sl7 删档二次确认双路径 - # oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(选档走查段——激活态建立组) + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (选档走查段——激活态建立组) # /sl8 消费方分派单口/sl12 动线序不回闪。开层=函数直调(「进入存档」钮归卡② mn3——中间态在案); # 隔离=attach_slot_template 注入命名空间,夹具按槽直写(卡① 注入口)。---- var csel_tpl := "user://smoke_click_probe_savesel_slot_%d.json" @@ -296,7 +296,7 @@ func _ready() -> void: _problems.append("[savesel] 关闭后层未隐藏或开始层未恢复(模态栈单口失守)") # ---- 设置面段(隔离 store 换注禁碰真实 user://settings.cfg;真实按钮信号路径)---- - # oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置面段——最小设置面组) + # oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (设置面段——最小设置面组) for p0 in ["user://smoke_click_probe_settings.cfg"]: if FileAccess.file_exists(p0): DirAccess.remove_absolute(p0) @@ -363,7 +363,7 @@ func _ready() -> void: if ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "退出游戏", "prefix") != null: _problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)") # ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写, - # oracle: design/gdd/ux/camp.md:45(生涯页段——同壳同形态组) + # oracle: design/gdd/ux/camp.md:45 (生涯页段——同壳同形态组) # 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)---- # 生涯页驱动=函数直调中间态(卡② mn10——careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径) if true: @@ -608,7 +608,7 @@ func _ready() -> void: _run_modal_probes_once() _play_one_action() # ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提 - # oracle: design/gdd/ux/level-map.md:11(主循环选路段——入口候选组) # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 + # oracle: design/gdd/ux/level-map.md:11 (主循环选路段——入口候选组) # (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至 # level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。 if not _done and not main.ui.modals.run.end_overlay.is_open(): var close_guard := 8000 @@ -653,7 +653,7 @@ func _ready() -> void: _play_one_action() # 排播期/Waiting 等待态=自然流逝(演播秒级、锁随队列空解除) # T17 适配:战败或放弃二途皆达终局面(放弃=预算耗尽收口路径——见上),断点段前提不变。 - # oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(断点段——断点域与续玩组) await get_tree().process_frame + # oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点段——断点域与续玩组) await get_tree().process_frame var bp_file := "user://smoke_click_probe_reset.json" var iso_chk = load("res://core/save_manager.gd").new(bp_file) iso_chk.load_from_disk() @@ -684,7 +684,7 @@ func _ready() -> void: # ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。 # ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)---- - # oracle: design/gdd/ux/camp.md:16(大门段——点大门先出确认组) + # oracle: design/gdd/ux/camp.md:16 (大门段——点大门先出确认组) # 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。 # 无断点=两出口(是/否);有断点=三出口(set_breakpoint 注入选路停点形——同下方开场行③先例)。 # 判据绑可观测提交面:点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 恒= @@ -809,6 +809,7 @@ func _ready() -> void: # ---- 模态/tooltip/刷新链一次性覆盖(真实信号路径驱动) ---- func _run_modal_probes_once() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:277 (判定上报通道——验证函数组) # ⓪ 玩家面文案真实入口断言(迭代36 卡A wa1——C 区两段头随战斗常驻) if not _tested_copy and main.ui.battle.info_panel != null and main.ui.battle.info_panel.visible: _tested_copy = true @@ -1051,7 +1052,7 @@ func _click_stop() -> void: # ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ---- -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236(锁期/重放段——拒绝与锁组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (锁期/重放段——拒绝与锁组) # 锁期断言(排播队列活跃=锁面):注入封印击 → 直落被冻结不受理(封印未置)+ `_locked_click` # 缓存命中且为本注入(「最后一次」语义——注入帧内无并发点击)。 # 重放断言(演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道):缓存清空 + @@ -1059,6 +1060,7 @@ func _click_stop() -> void: # 前提消失守卫:等重放期间出现停点层/战斗收场(注入时点恰逢终结演播)→ 静默弃测不计 # problem(封印动作不杀伤,正常局不触发;断言实现形态取舍在代拍白名单内)。 func _locktest_tick() -> void: +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (判定上报通道——验证函数组) if _locktest_state == 2: return var settlement = main.run_scope.settlement diff --git a/game/probe/ui_contrast_probe.gd b/game/probe/ui_contrast_probe.gd index 860c9d37..4459417e 100644 --- a/game/probe/ui_contrast_probe.gd +++ b/game/probe/ui_contrast_probe.gd @@ -88,7 +88,7 @@ func _ratio(a: Color, b: Color) -> float: func _ready() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(UI 文字对比度机检 ≥4.5:1 无区域例外——对比度组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281 (UI 文字对比度机检 ≥4.5:1 无区域例外——对比度组) var problems: Array = [] var pairs := _pairs() for p in pairs: diff --git a/game/probe/ui_input_probe.gd b/game/probe/ui_input_probe.gd index 968124c5..8b08983a 100644 --- a/game/probe/ui_input_probe.gd +++ b/game/probe/ui_input_probe.gd @@ -759,6 +759,7 @@ func _push_release(pos: Vector2, btn: int) -> void: func _pre_fail(why: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) push_error("[ui_input_probe] FAIL — " + why) print("[ui_input_probe] FAIL — " + why) get_tree().quit(1) diff --git a/game/tests/integration/cross_system_flow_test.gd b/game/tests/integration/cross_system_flow_test.gd index 22c1b78d..5fa4681b 100644 --- a/game/tests/integration/cross_system_flow_test.gd +++ b/game/tests/integration/cross_system_flow_test.gd @@ -20,6 +20,7 @@ var _db func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2–G5 同款:表损坏的红落在具体断言上) var load_ok: bool = _db.load_tables("res://data/tables", true) @@ -46,7 +47,7 @@ func _fresh_rng(seed_val: int): # ---- ① RNG 事件日志会话落盘(源 `_initialize` :70,主会话裁决一改判集成—— # 驱动沿源 :60–70 取号序列自持:master 种子 42 → 前置取号 → new_adventure 开日志会话 → 会话内取号)---- func _rng_event_log_session() -> void: -# oracle: design/engineering/architecture/architecture.md:90(事件日志落盘会话组) +# oracle: design/engineering/architecture/architecture.md:90 (事件日志落盘会话组) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) rng.open_stream("board_gen") @@ -61,7 +62,7 @@ func _rng_event_log_session() -> void: # _on_level_finished(victory)+breakpoint_snapshot 取读+clear_breakpoint; # 起局与兑付两步的受理断言留单元宿主(adventure_feats_test),此处纯驱动——前置失败自然落红)---- func _feats_breakpoint_roundtrip() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(专长断点附随件往返组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (专长断点附随件往返组) var rt := FeatTable.new() rt.rows = [ {"feat_id": "x1", "display_name": "试一", "rarity": "common", "desc": "测试揭示", "cap": 1, @@ -107,7 +108,7 @@ func _feats_breakpoint_roundtrip() -> void: # level_autoplay 真局摘要 → apply 原语 → 域读数 → 写盘读回保持;临时档 user://smoke_save_integration.json # 段首清场+段尾清理(源 _smoke_save 隔离纪律),绝不碰 user://save.json(红线))---- func _save_summary_integration() -> void: -# oracle: design/gdd/systems/跨局循环/元进度.md:150(摘要集成与钥石只增不减组) +# oracle: design/gdd/systems/跨局循环/元进度.md:150 (摘要集成与钥石只增不减组) var sm_script = load("res://core/save_manager.gd") var path9 := "user://smoke_save_integration.json" # 源 paths[8] if FileAccess.file_exists(path9): @@ -140,7 +141,7 @@ func _save_summary_integration() -> void: # ---- ④ 试玩记录仪(源 `_smoke_playtest_logger` 整套八条,主会话裁决七改判集成—— # PlaytestLogger 挂真实结算流:会话文件/逐结算快照/字段齐备/spawn_count 回归/诊断转储/JSONL 回读)---- func _smoke_playtest_logger() -> void: -# oracle: design/engineering/code-conventions.md:40(试玩日志器跨系统接线组) +# oracle: design/engineering/code-conventions.md:40 (试玩日志器跨系统接线组) print("--- PlaytestLogger(结算快照 JSONL + 诊断转储)---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260912) diff --git a/game/tests/integration/node_map_determinism_test.gd b/game/tests/integration/node_map_determinism_test.gd index 0ddcd4cb..d00e2215 100644 --- a/game/tests/integration/node_map_determinism_test.gd +++ b/game/tests/integration/node_map_determinism_test.gd @@ -19,6 +19,7 @@ var _bp_path := "user://smoke_node_map_save.json" func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _db = load("res://core/config_db.gd").new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上) var load_ok: bool = _db.load_tables("res://data/tables", true) @@ -33,7 +34,7 @@ func _init() -> void: # ---- 关卡节点图生成·确定性承载断言面 ---- func _smoke_generated_map_determinism() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(起关/选路/断点重生成同图组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (起关/选路/断点重生成同图组) # ① 纯函数复现:图 = f(参数档, 种子)——同档同种子两次产图逐位同图,换种子必变图 var gen = NodeMapGenerator.new(_fresh_rng(910001)) var params2: Dictionary = _db.run.level_gen_params.row_by_level_index(2) diff --git a/game/tests/integration/save_rng_roundtrip_test.gd b/game/tests/integration/save_rng_roundtrip_test.gd index ade5b288..d2bda306 100644 --- a/game/tests/integration/save_rng_roundtrip_test.gd +++ b/game/tests/integration/save_rng_roundtrip_test.gd @@ -20,6 +20,7 @@ var _disk: String func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _disk = ProjectSettings.globalize_path(SAVE_PATH) _clear_disk() _db = load("res://core/config_db.gd").new() as ConfigDb @@ -35,7 +36,7 @@ func _init() -> void: # ---- 用例一:盘上往返保真——写档再读回,断点逐键零漂移 ---- func _test_disk_roundtrip_preserves_breakpoint() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(存档⇄RNG 快照往返——断点域逐位组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (存档⇄RNG 快照往返——断点域逐位组) var rng := RngServiceScript.new() var sm := SaveManagerScript.new(SAVE_PATH) var adr := AdventureRunnerScript.new(_db, rng) @@ -66,7 +67,7 @@ func _test_disk_roundtrip_preserves_breakpoint() -> void: # ---- 用例二:旧版本档经迁移链后仍可续玩(单测不覆盖的跨系统集成面)---- func _test_old_version_save_migrates_and_resumes() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:179(旧档迁移链读回续玩组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:179 (旧档迁移链读回续玩组) var rng := RngServiceScript.new() var sm := SaveManagerScript.new(SAVE_PATH) var adr := AdventureRunnerScript.new(_db, rng) diff --git a/game/tests/smoke_boot.gd b/game/tests/smoke_boot.gd index eed5e76e..40b4f767 100644 --- a/game/tests/smoke_boot.gd +++ b/game/tests/smoke_boot.gd @@ -16,7 +16,7 @@ var _checks: int = 0 func check(cond: bool, label: String) -> void: -# oracle: game/tests/README.md:366(冒烟宿主判据通道——存活检查组) +# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组) _checks += 1 if cond: print(" [PASS] " + label) @@ -35,7 +35,7 @@ func _ready() -> void: # 开机前置面:autoload 注册与资源可加载(挂主屏之前先查,失败时主屏断言整段跳过) func _check_autoloads() -> void: -# oracle: game/tests/README.md:366(autoload 在位组) +# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组) for autoload_name in ["ConfigDB", "RngService", "SaveManager", "MetaProgression"]: var node: Node = get_node_or_null(NodePath("/root/" + autoload_name)) check(node != null, "autoload %s 在位" % autoload_name) @@ -64,7 +64,7 @@ func _boot_main_scene() -> Node: # 主屏存活面:组合根装配句柄在位(main.X 一级句柄契约,探针挂载假设的启动侧证明) func _check_main_scene(main: Node) -> void: -# oracle: game/tests/README.md:366(主屏可达组) +# oracle: design/engineering/architecture/architecture.md:10 (测试层级四级——冒烟存活检查组) check(main != null and main.is_inside_tree(), "主屏场景已实例化并在树") if main == null: return diff --git a/game/tests/system_test.gd b/game/tests/system_test.gd index b98c626a..c9c951b1 100644 --- a/game/tests/system_test.gd +++ b/game/tests/system_test.gd @@ -33,6 +33,7 @@ func _initialize() -> void: # (自 game/smoke/smoke_all.gd 剪切迁入——断言 label 逐字原样;G12 两套调用不随迁, # 归 source_guard_test.gd,断言迁移批 B G1) func _split_tail() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) await process_frame await process_frame var autoload_ok: bool = get_root().get_node_or_null("/root/ConfigDB") != null and get_root().get_node_or_null("/root/SaveManager") != null @@ -51,6 +52,7 @@ func _split_tail() -> void: # ---- 三屏场景化断言(V2——独立实例化+关键节点在位+组合根树序/z 序保序)---- # (自 game/smoke/smoke_all.gd 剪切迁入——label 与 cond 逐字原样,断言迁移批 B G1) func _smoke_scene_split() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 迭代27 T1 三屏场景化(V2:屏壳独立实例化+组合根装配保序)---") # ① 三屏壳独立实例化(F6 等价——headless 直挂;视图代码构建在屏壳 _ready) var menu: Node = load("res://presentation/scenes/menu_screen.tscn").instantiate() @@ -147,6 +149,7 @@ func _count_visible_buttons(node: Node) -> int: # ---- boot 链路时序后置条件(启动态六断言;只立行为可判断言,不造假时序)---- func _smoke_boot_chain() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 迭代28 T2 boot 链路时序后置条件(V2:六断言)---") var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) @@ -211,6 +214,7 @@ func _smoke_boot_chain() -> void: func _smoke_run_lifecycle() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 迭代27 T1 会话物化(V3:两连局复位+脏状态不残留)---") var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) @@ -268,6 +272,7 @@ func _smoke_run_lifecycle() -> void: # ---- d6/d7 批量模拟互斥守卫(wd6/wd7——白盒置态直调入口,以守卫早退的可见后果为断言面)---- func _smoke_sim_guards() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 卡① T2:批量模拟互斥两守卫(d6/d7)---") var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) @@ -341,6 +346,7 @@ func _face_snapshot(main: Node) -> Dictionary: # 阶段面断言:adv/run/set=alive|null|both(会话三面存活态);恒活面每阶段必查; # 现值回调与 run_scope 成员必须同源(回调链无悬挂铁证);extra=阶段附加判据。 func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String, extra: Dictionary = {}) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var snap := _face_snapshot(main) var bad := "" for k in ["bonus_meta", "bonus_save", "camp_meta", "camp_save", "rp_log", "rp_stage", "rp_board_zone", "rp_fx", "rp_board"]: @@ -409,7 +415,7 @@ func _face_liveness(name: String, v, want: String) -> String: #(level_started→choices_presented 同步到达,无可观测中间态)——合并为 V3b 一处断言。 # (自 game/smoke/smoke_all.gd 剪切迁入——label 与 cond 逐字原样,断言迁移批 B G1) func _smoke_lifecycle_matrix() -> void: -# oracle: design/gdd/systems/冒险循环/冒险结构.md:284(生命周期矩阵——三关序列组) +# oracle: design/gdd/systems/冒险循环/冒险结构.md:284 (生命周期矩阵——三关序列组) print("--- 迭代28 T3 生命周期存活性矩阵(V3:九阶段×五引用面)---") var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) diff --git a/game/tests/unit/battle_settlement/damage_controller_test.gd b/game/tests/unit/battle_settlement/damage_controller_test.gd index 250a3cda..8807bd03 100644 --- a/game/tests/unit/battle_settlement/damage_controller_test.gd +++ b/game/tests/unit/battle_settlement/damage_controller_test.gd @@ -71,7 +71,7 @@ func _row_by_display_prefix(prefix: String) -> Dictionary: # ---- 五配方一次全流程 + 双跑逐位复现 ---- func _run_recipe_sequence(db) -> Array: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(五段全序——配方序列组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (五段全序——配方序列组) _rows = [] var h = _harness(db) var units = h.units @@ -178,7 +178,7 @@ func _run_recipe_sequence(db) -> Array: func _test_five_recipes_and_replay(db) -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:363(同种子回放逐行相同组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:363 (同种子回放逐行相同组) var run1: Array = _run_recipe_sequence(db) Kit.check("五配方序列产出 5 行伤害行", run1.size() == 5) var run2: Array = _run_recipe_sequence(db) @@ -188,7 +188,7 @@ func _test_five_recipes_and_replay(db) -> void: # ---- A 模式(开单即结——回切位) ---- func _test_policy_immediate_mode(db) -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算策略即时面组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (结算策略即时面组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -204,7 +204,7 @@ func _test_policy_immediate_mode(db) -> void: # ---- 来源敌死亡:队列中未结单据作废(死亡即不动手) ---- func _test_source_death_void(db) -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:133(摇定后兑现前被杀本拍作废组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:133 (摇定后兑现前被杀本拍作废组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -238,7 +238,7 @@ func _unit_fixture() -> Dictionary: func _test_identical_content_dual_enemy(db: ConfigDb) -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:303(双敌同内容顺位组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:303 (双敌同内容顺位组) var a := _unit_fixture() var b := _unit_fixture() Kit.check("前置:双敌字典内容全同且非同一实例", a == b and not is_same(a, b)) @@ -268,6 +268,7 @@ func _test_identical_content_dual_enemy(db: ConfigDb) -> void: # d12 产发端增键:目标身份随行/随载荷下发(同名敌按 id 精确解析的产发半) func _test_target_id_payload(db: ConfigDb) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -290,6 +291,7 @@ func _test_target_id_payload(db: ConfigDb) -> void: func _test_requires_gate_and_zero(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -316,6 +318,7 @@ func _test_requires_gate_and_zero(db) -> void: # ---- 主角倒下:flush 余单全部作废(_halted 终止) ---- func _test_hero_death_halt(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -333,6 +336,7 @@ func _test_hero_death_halt(db) -> void: # ---- AC 合流:override 入参 / 表值兜底 / ac 型状态叠层(不入池) ---- func _test_ac_merge(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -360,6 +364,7 @@ func _test_ac_merge(db) -> void: # ---- 池乘区收口:池内加算 → clamp;ac 型不入池 ---- func _test_pool_clamp(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _rows = [] var h = _harness(db) if not h.spawn_ok: @@ -417,6 +422,7 @@ func _test_pool_clamp(db) -> void: # ---- 个体池读数 / 个体 AC 合流(增益状态 v1.5——敌方状态个体化批)---- func _test_pool_consumption_per_unit(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var h = _harness(db, 3) # depth 3 = 混编行集(熊地精 1 + 地精 1–2,最少 2 只——个体对照面需 ≥2 敌) if not h.spawn_ok: return @@ -436,6 +442,7 @@ func _test_pool_consumption_per_unit(db) -> void: func _test_defender_ac_per_unit(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var h = _harness(db, 3) if not h.spawn_ok: return diff --git a/game/tests/unit/battle_settlement/damage_math_test.gd b/game/tests/unit/battle_settlement/damage_math_test.gd index e5d9114f..8e468109 100644 --- a/game/tests/unit/battle_settlement/damage_math_test.gd +++ b/game/tests/unit/battle_settlement/damage_math_test.gd @@ -12,7 +12,7 @@ var db func _init() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(伤害公式宿主——装载组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (伤害公式宿主——装载组) db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("前置:settlement 表加载零 error", load_ok and db.diagnostics_count("error") == 0) @@ -31,7 +31,7 @@ func _floor_val() -> int: # ---- F2 乘区:池内加算 → 池间乘算 → floor(单一取整规则)---- func _test_pool_damage() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(池伤害公式组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (池伤害公式组) var d := _floor_val() + 9 # 表值推导的任意正探针量(非玩法行值) Kit.check("中性双池 (0,0) 原样透传", DamageMathScript.pool_damage(d, 0, 0) == d) Kit.check("负敌方池压低不增高", DamageMathScript.pool_damage(d, 0, int(db.combat.settlement.core.pool_clamp_lo)) <= d) @@ -44,7 +44,7 @@ func _test_pool_damage() -> void: # ---- 克制分量:空桶不吃修正恒 0;落地下限保底([1,+∞) 全来源)---- func _test_counter_component() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(计数器分量组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (计数器分量组) var floor_v := _floor_val() Kit.check("空桶(点数≤0)恒 0 不吃修正", DamageMathScript.counter_component(0, floor_v, floor_v) == 0 and DamageMathScript.counter_component(-1, 5, floor_v) == 0) @@ -56,7 +56,7 @@ func _test_counter_component() -> void: # ---- 掷点折扣查档(纯函数):满阈值恒全额;档位边界/末档语义与表数组一致 ---- func _test_roll_discount_percent() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(乘区折扣组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (乘区折扣组) var threshold := 10 # 查档探针阈值(夹具值——档位参数本身全部来自表) var bounds: Array = db.combat.settlement.damage_roll.roll_tier_bounds var discounts: Array = db.combat.settlement.damage_roll.roll_tier_discounts @@ -81,7 +81,7 @@ func _test_roll_discount_percent() -> void: # ---- 吃掷点分量算术:折扣→(暴击)→floor→+克制修正(单一取整链)---- func _test_rolled_component() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(掷点分量组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点分量组) var base := float(_floor_val() + 7) # 表值推导探针基线 var crit_mult: int = int(db.combat.settlement.damage_roll.crit_multiplier) Kit.check("满额折扣无暴击 = floor(基线) + 修正 0", DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base))) diff --git a/game/tests/unit/battle_settlement/effect_port_test.gd b/game/tests/unit/battle_settlement/effect_port_test.gd index a8948a1e..9394c55c 100644 --- a/game/tests/unit/battle_settlement/effect_port_test.gd +++ b/game/tests/unit/battle_settlement/effect_port_test.gd @@ -19,7 +19,7 @@ var _db func _init() -> void: -# oracle: design/engineering/architecture/architecture.md:144(效果端口宿主——装载组) +# oracle: design/engineering/architecture/architecture.md:144 (效果端口宿主——装载组) _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) @@ -54,7 +54,7 @@ func _hero_attack_value(rows: Array) -> int: # ---- V1 端口单实例与真实发射路由 ---- func _smoke_effect_port_wiring() -> void: -# oracle: design/engineering/architecture/architecture.md:144(端口单实例与六 Callable 路由组) +# oracle: design/engineering/architecture/architecture.md:144 (端口单实例与六 Callable 路由组) print("--- 效果端口:装配链单实例+真实发射路由 ---") # 充能跑(蓄力周转 charge_rotation 在册——attack_commit 订阅行 attack_bonus per 蓄力周转计数器) var orch_c = _orch(TEST_SEED) @@ -88,7 +88,7 @@ func _smoke_effect_port_wiring() -> void: # ---- V2 业务类零引擎直连(源码契约锚——theme_projection_test 源码锚惯例)---- func _test_business_classes_engine_free() -> void: -# oracle: design/engineering/architecture/architecture.md:144(业务类零引擎直连组) +# oracle: design/engineering/architecture/architecture.md:144 (业务类零引擎直连组) print("--- 效果端口:业务类零引擎直连源码锚 ---") for path in ["res://core/damage_controller.gd", "res://core/action_economy.gd"]: var src := FileAccess.get_file_as_string(path) @@ -100,7 +100,7 @@ func _test_business_classes_engine_free() -> void: # ---- V3 伤害域事实面闭集(合成行两对照——damage_taken 真实单据流)---- func _test_biz_fact_face_closed() -> void: -# oracle: design/engineering/architecture/architecture.md:144(闭面四键 biz_fact 组) +# oracle: design/engineering/architecture/architecture.md:144 (闭面四键 biz_fact 组) print("--- 效果端口:业务发射事实面闭集 ---") var orch = _orch(TEST_SEED) Kit.check("V3 begin_battle 受理", orch.begin_battle(_enc([]))) diff --git a/game/tests/unit/battle_settlement/extended_row_violation_test.gd b/game/tests/unit/battle_settlement/extended_row_violation_test.gd index 827eed8b..fd3ac2c5 100644 --- a/game/tests/unit/battle_settlement/extended_row_violation_test.gd +++ b/game/tests/unit/battle_settlement/extended_row_violation_test.gd @@ -11,7 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(扩展行必载 effect_id——加载致命组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (扩展行必载 effect_id——加载致命组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): push_error("[extended_row_violation_test] 表加载失败") diff --git a/game/tests/unit/battle_settlement/refresh_feats_test.gd b/game/tests/unit/battle_settlement/refresh_feats_test.gd index 958427cb..01fc0c08 100644 --- a/game/tests/unit/battle_settlement/refresh_feats_test.gd +++ b/game/tests/unit/battle_settlement/refresh_feats_test.gd @@ -154,7 +154,7 @@ func _seal_direct_with_expansion(orch) -> Vector2i: # 四条真实路径(敌方攻击/危险格/错误封印/地形)逐路驱动:每条至少一张主角承伤单据在案, # 且承伤计数器 == 主角承伤单据行数(每单据恰发射一次的全量判据——重复发射即计数器>行数)。 func test_all_hero_damage_paths_emit_once() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图生存:全局固定减伤全伤害路径恰一次组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图生存:全局固定减伤全伤害路径恰一次组) print("--- V5 全局 damage_taken:四路径逐路恰一次 ---") # ① 敌方攻击路径(Boss 战零挖掘连停三轮——干净样本:零地形零资源单据;多轮保证伤害技在案) var orch1 = _orch(46001) @@ -224,7 +224,7 @@ func _plan_seal(orch, target: Vector2i) -> Dictionary: # 同一伤害不得出现两次消费(重复发射=计数器>行数):敌方路径与地形路径两样本逐位核对—— # 地形单据带 hero_damage_consumed 标记,战斗段末 _resolve_board_flat_hero 跳过二次发射由此钉住。 func test_no_duplicate_damage_taken_event() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(受击事件零重复组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (受击事件零重复组) print("--- V5 全局 damage_taken:无重复消费 ---") var orch1 = _orch(46011) var rows1 := _collect_hero_rows(orch1) @@ -266,7 +266,7 @@ func _auto_dig(orch) -> void: # V2 矿脉储能:受理的付费挖格 RAP 恰 +1、AP 按表扣、板资源不变 func test_reserve_on_accepted_dig() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(换图基座:挖格产 RAP 组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 组) print("--- V2 矿脉储能:付费挖格产 RAP ---") var orch = _orch(46021) Kit.check("V2 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve"]))) @@ -304,7 +304,7 @@ func _drain_round_rap_accounting(orch) -> bool: # V3 省力周转:折价门槛与扣费(can 与执行同源;base 8→扣 4) func test_refresh_discount_gate_and_charge() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(刷新折扣门控与充能组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (刷新折扣门控与充能组) print("--- V3 省力周转:折价门槛与扣费 ---") var orch = _orch(46022) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["easy_swap"]))) @@ -326,7 +326,7 @@ func test_refresh_discount_gate_and_charge() -> void: # V3 双基座叠加:挖格产 RAP 与刷新折价同场生效 func test_double_base_stack() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(双基座叠加组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (双基座叠加组) print("--- V3 双基座叠加 ---") var orch = _orch(46023) Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve", "easy_swap"]))) @@ -343,7 +343,7 @@ func test_double_base_stack() -> void: # V12(refresh_feats 侧):无专长基线——挖格零产 RAP、刷新按表基线扣、AP 保留、上限照旧 func test_no_feats_baseline() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(零专长基线组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (零专长基线组) print("--- V12 无专长基线(refresh_feats 侧) ---") var orch = _orch(46024) Kit.check("V12 前置:战斗受理", orch.begin_battle(_enc([]))) @@ -379,7 +379,7 @@ func _br_count(orch) -> int: # V4 成功事实只在新板就位后发射:成功恰一次;非 Active/每轮上限/RAP 不足三拒面全零事实零换图 func test_board_refresh_fact_only_on_success() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(弃图刷新事实面仅成功态组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (弃图刷新事实面仅成功态组) print("--- V4 成功刷新事实:拒绝零事实 ---") _install_br_probe() var orch = _orch(46031) @@ -414,7 +414,7 @@ func test_board_refresh_fact_only_on_success() -> void: # V10 矿道红利:每次成功刷新 escrow 恰 +1 且 fx_grant_gp 行在案;拒绝零变化 func test_mine_profit_gp() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图资源:刷新固定 GP 收益组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图资源:刷新固定 GP 收益组) print("--- V10 矿道红利:刷新固定 GP ---") var orch = _orch(46032) var gp_rows := {"n": 0} @@ -461,7 +461,7 @@ func _damage_rows_by_source(rows: Array, source: String) -> Array: # V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减; # 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射) func test_stone_mitigation_all_damage() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(全局减伤全覆盖组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (全局减伤全覆盖组) print("--- V6 石层缓冲:全局固定减伤 ---") # S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死) var orch1 = _orch(46041) @@ -517,7 +517,7 @@ func test_stone_mitigation_all_damage() -> void: # V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入; # 自然累计 2 与满血未生效两形态分战场钉死) func test_rest_rotation_cap_and_clear() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(轮末恢复 cap 与清账组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (轮末恢复 cap 与清账组) print("--- V9 坑道小憩:轮末恢复 ---") # R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3 var orch1 = _orch(46051) @@ -575,7 +575,7 @@ func test_rest_rotation_cap_and_clear() -> void: # V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空) func test_charge_layers_consumed_same_round() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(充能当轮消耗组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (充能当轮消耗组) print("--- V8 蓄力周转:攻击消费层 ---") var orch1 = _orch(46061) var rows1 := _collect_damage_rows(orch1) @@ -602,7 +602,7 @@ func test_charge_layers_consumed_same_round() -> void: # V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账) func test_blast_defers_to_combat_segment() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(刷新群伤让位战斗段组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (刷新群伤让位战斗段组) print("--- V7 崩壁冲击:延迟群伤 ---") var orch = _orch(46071) var rows := _collect_damage_rows(orch) @@ -621,7 +621,7 @@ func test_blast_defers_to_combat_segment() -> void: # 七件 validate/在册/稀有度 cap 对表/跨表计数器引用闭合/desc 禁词 func test_seven_feats_validate_and_pool() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(换图系七件校验与池组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (换图系七件校验与池组) print("--- V11 七件数据与池 ---") var seven := {"vein_reserve": "excellent", "easy_swap": "excellent", "wall_blast": "excellent", "charge_rotation": "excellent", "stone_mitigation": "common", "tunnel_rest": "common", "mine_profit": "rare"} diff --git a/game/tests/unit/battle_settlement/roll_multiplier_test.gd b/game/tests/unit/battle_settlement/roll_multiplier_test.gd index 3096787f..50c6ff17 100644 --- a/game/tests/unit/battle_settlement/roll_multiplier_test.gd +++ b/game/tests/unit/battle_settlement/roll_multiplier_test.gd @@ -19,7 +19,7 @@ var _db func _init() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(掷点与乘区宿主——装载组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (掷点与乘区宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -44,7 +44,7 @@ func _init() -> void: # 受理后不变量(encounter 受理后只读)。攻击单 base_value 贯通由 _smoke_damage_types 的 # hero_atk_val=100 全链路行值承载(同夹具既有覆盖——派生读在攻击链上工作即行值随 override 走)---- func _smoke_hero_atk_derived() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(英雄攻击派生组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (英雄攻击派生组) var rng_a = load("res://core/rng_service.gd").new() rng_a.set_master_seed(4242) var s_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a) @@ -67,7 +67,7 @@ func _smoke_hero_atk_derived() -> void: # → heavy 伤害 == light −1、ward == light +1(要求基线伤害 ≥ 2——地板 1 会吞 −1 位移)。 # 承伤位移红线(卡片 §8 L3):全局位移只允许 ±1 量级——非此量级 = 矩阵消费面有未盘点的第二落点(停机)。 func _smoke_enemy_attack_type() -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:295(敌攻击类型行集组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:295 (敌攻击类型行集组) print("--- 敌方攻击类型轴(迭代 24 N3)---") # ① 表列:20 行全填 normal(N3 全填不做差异化——Q9) var at_ok := true @@ -126,7 +126,7 @@ func _smoke_enemy_attack_type() -> void: # ---- 护甲等级掷点·行为面(折扣档分布 / nat20 暴击 / 伤害下限 / 棋盘来源必中)---- func _smoke_armor_roll() -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:254(护甲掷点与叠层合并组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:254 (护甲掷点与叠层合并组) print("--- 护甲等级掷点(查档纯函数 + 暴击 + 伤害下限 + AC12 分布 + 棋盘必中)---") # ① 查档纯函数:全掷点域逐位对照(AC10 / AC12)+ EV 锚(AC10 期望减免 21.25% —— registry 参考数) var expect_ac10 := {100: 11, 75: 3, 50: 4, 25: 2} # 掷 10-20 全额 / 7-9 / 3-6 / 1-2 @@ -345,6 +345,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void: # 基线构成:基线 100——hero 攻击类型=normal(锻造工坊配置默认值, # hero.tres 无两轴字段)对轻甲中性 0 修正(目标 = L1 地精轻甲;AC 0 恒全额非暴击种子)。 func _smoke_pool_wiring() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 主角乘区接线 + 硬化折半方向 ---") var base_row := _hero_attack_row_with({}, 100, 20260918) var up_row := _hero_attack_row_with({"effects": ["attack_up"]}, 100, 20260918) @@ -386,6 +387,7 @@ func _hero_attack_row_with(opts: Dictionary, hero_atk_val: int, seed_val: int) - # ---- 攻击类型 × 护甲克制 + 标错惩罚冒烟(护甲快照/克制矩阵/充能/反噬三选一)---- func _smoke_damage_types() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 攻击类型 × 护甲克制 + 标错惩罚 ---") # ① 护甲类型快照传播(spawn 行值 → 单位字典) var enemy_system = load("res://core/unit_container.gd").new(_db) @@ -480,6 +482,7 @@ func _find_seal_target(minesweeper, want_mine: bool) -> Dictionary: # d_tick = max(damage_floor, pool_damage(floor(基线×档位%), 0, 0)),档位读数随行重建可归因)---- func _smoke_attack_chain_fresh_pool() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 战斗结算·fresh 池攻击链回归(Bug#2·掷点口径)---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260913) 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 3f2fe528..d3a7a1ef 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 @@ -22,7 +22,7 @@ var _db func _init() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:360(轮内流与技能池宿主——装载组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:360 (轮内流与技能池宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -48,7 +48,7 @@ func _init() -> void: # ---- 完整战斗回放(自动玩家,确定性)——回放断言:同种子两遍步骤流逐行相同 ---- func _smoke_full_battle(L: int, D: int, label: String) -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(全战斗回放——五段全序组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (全战斗回放——五段全序组) print("--- 完整战斗回放:%s ---" % label) var run1 = _run_battle(L, D) var run2 = _run_battle(L, D) @@ -83,7 +83,7 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(战斗驱动组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (战斗驱动组) # 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -148,7 +148,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void: # ---- 行动经济·轮内刷新冒烟(方案 A:Active 相位当场换图继续挖)---- func _smoke_active_refresh() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:262(停手转存储备与轮内刷新组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:262 (停手转存储备与轮内刷新组) print("--- 行动经济·轮内刷新(方案 A)---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260912) @@ -255,7 +255,7 @@ func _active_refresh_replay() -> Array: # ---- 遭遇随机化冒烟(难度曲线与Boss Core Rule 8:区间骰/encounter 流/可回放/空表定值路径)---- func _smoke_encounter_roll() -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:296(遭遇区间骰——摇号确定性组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:296 (遭遇区间骰——摇号确定性组) print("--- 遭遇随机化(unit_count 区间骰)---") var counts: Array = [] for i in range(20): @@ -390,6 +390,7 @@ func _encounter_replay(seed_val: int) -> Array: # 五段全序断言(战斗结算 验收"五段全序"+ 行型封闭 10 类):逐轮行相位序 = 授予 → 增益/减益 → 战斗 → 衰减 → 判定。 func _smoke_settlement_segments() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 五段全序 + 行型封闭 ---") Kit.check("segment_order 五段冻结在位(表值 = 拍板序恒等)", Array(_db.combat.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER @@ -508,6 +509,7 @@ func _smoke_skill_pool() -> void: # ① 数据面:技能表行集 + 普攻锁死 + 建议池行值 + 空池不可能(结构断言) func _smoke_skill_pool_data() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) Kit.check("技能表十六行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕+ 触发条件批示范两行〔孤注守势/趁隙偷袭〕)", _db.combat.skill.rows.size() == 16) var basic: Dictionary = _db.combat.skill.row_by_id("attack") Kit.check("普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)", @@ -722,6 +724,7 @@ func _fresh_rng(seed_val: int): # 单敌轨迹不独立复放——轨迹取数与其它流交错,无法脱离全序重建; # 同种子全战斗逐位复现由 ② 全对拍承载,单点定位归事件日志。 func _smoke_skill_pool_replay_independence() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var seed_val := -1 for s in range(1, 60): var seqs := _record_pool_trajectories(s, 1) @@ -775,6 +778,7 @@ func _record_pool_trajectories(seed_val: int, rounds: int) -> Dictionary: # 2026-10-28 重锚 [0.82, 1.02](照酋长先例 ±0.10 绝对幅度,中值取理论稳态 0.92;旧带 [1.00,1.20] 系触发器前 # 唯一特殊技=突刺的池结构下登记,随池构成换代作废;撤销证明=带宽回退旧值本断言复红)→ 宽带登记。 func _smoke_skill_pool_ev_anchor() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var bands := {"goblin": [0.82, 1.02], "bugbear": [0.97, 1.03], "ogre_chief": [1.10, 1.30]} # 地精重锚 2026-10-28(触发条件批孤注守势入池稀释,无情境口径实测 0.9148;EV 锚注 enemy.tres)· 酋长重锚(震地怒击半血解锁入池,含闸长期速率上移:实测 1.1948;EV 锚注 enemy.tres) var species := ["goblin", "bugbear", "ogre_chief"] var rng = load("res://core/rng_service.gd").new() @@ -830,6 +834,7 @@ func _smoke_skill_pool_ev_anchor() -> void: # ⑤ 死亡打断作废(摇定后、兑现前死亡 → 本拍技能作废、不动手、不进账) func _smoke_skill_pool_death_void() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var void_seed := -1 for s in range(1, 400): var rg = load("res://core/rng_service.gd").new() @@ -869,6 +874,7 @@ func _smoke_skill_pool_death_void() -> void: # ⑥ 加载校验拒绝(单表域校验 + 跨表池构成纪律) func _smoke_skill_pool_validation_reject() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var skill_table = load("res://data/schemas/skill_table.gd").new() var enemy_table = load("res://data/schemas/enemy_table.gd").new() enemy_table.rows.assign([{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "ghost", "weight": 1}]}]) @@ -927,6 +933,7 @@ func _has_err_category(errs: Array, category: String) -> bool: # 停手轮驱动 (2,9)——零挖掘零封印 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行 # 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。 func _smoke_debuff_rider() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var venom_seed := -1 var venom_check_ok := true var venom_rows_seen := 0 @@ -997,6 +1004,7 @@ func _smoke_debuff_rider() -> void: # ---- 天赋树六键行为冒烟(V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿; # 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))---- func _smoke_counter_buckets() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 效果引擎无状态化四桶正向验收(卡② §9 V1/V6)---") # V1——adventure 桶跨战持久:同一宿主字典下两战各真实 request_dig 一次, # 断付费计数经 ctx 就地写入宿主字典并跨战累积(副本搬运态:读不到上一战值→第 2 战仍 1,此断言会红)。 @@ -1059,6 +1067,7 @@ func _smoke_counter_buckets() -> void: # V6 形态先例同源:直入真实管线缝(_judge_seal 直调同款口径)。 func _smoke_extended_rows() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) print("--- 扩展行收敛:fx_reveal 兑付/落空 ---") var RigS = load("res://core/battle_orchestrator.gd") # 局部装配别名(_smoke_counter_buckets 同款——跨函数不共享局部量) var enc_fx = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, @@ -1104,6 +1113,7 @@ func _smoke_extended_rows() -> void: # 蓄力表值 2 拍衰减后剩 1 拍仍在池)→ 逐敌读敌方输出池。判据=施放者与被他敌的池读数差。 func _smoke_enemy_buff_targets_self() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var emp_mag: int = int(_db.combat.status_effects.row_by_id("empower").magnitude) var sc_script = load("res://core/status_container.gd") var found_seed := -1 diff --git a/game/tests/unit/battle_settlement/seal_polarity_test.gd b/game/tests/unit/battle_settlement/seal_polarity_test.gd index 70178b65..71d92b31 100644 --- a/game/tests/unit/battle_settlement/seal_polarity_test.gd +++ b/game/tests/unit/battle_settlement/seal_polarity_test.gd @@ -22,7 +22,7 @@ var _db func _init() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印与极性宿主——装载组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印与极性宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -46,7 +46,7 @@ func _init() -> void: # ---- 末点 AP 封印轮末结算(playtest 缺陷②回归:末点 AP 花在封印必须终结轮次进入下一轮)---- func _smoke_seal_ap_exhausted() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:260(末点 AP 耗尽即收轮组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:260 (末点 AP 耗尽即收轮组) print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260911) @@ -324,8 +324,8 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array: func _smoke_clear_reward_chain() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(清图奖励链——清图槽对象身份组) -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(清图奖励链——清图槽对象身份组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (清图奖励链——清图槽对象身份组) print("--- 清图奖励·双份兑付链(完美封印 / 零兑付 / 第二份被拒 / 高光记实)---") # ① 真实流完美封印:标中一面真雷封印 + 零误标 + 清全图 → grant_clear 身份(clear_blast)双份真实兑付 var rows: Array = [] @@ -354,8 +354,8 @@ func _smoke_clear_reward_chain() -> void: # 弱形式登记:正常战斗流下"授段时刻无存活敌"不可达——敌全灭当轮判定段即判胜并终止战斗,而清图批在判定段之前, # 恒有存活敌;故本组直调编排私有面构造状态,只断言"路径存在 + 行为符合契约"(构造手法见各行注释)。 func _smoke_clear_reward_zero_paths() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240(无存活敌群伤记「奖励未生效」组) -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240(无存活敌群伤记「奖励未生效」组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:240 (无存活敌群伤记「奖励未生效」组) # ②-a 首份零兑付判定(无存活敌)→ first_ok=false + 未生效行 var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260915) @@ -410,8 +410,8 @@ func _smoke_clear_reward_zero_paths() -> void: # 轮内刷新 provenance 守卫:刷新换来的图同轮清图不发清图奖励(_board_from_midround_refresh 守卫) func _smoke_refresh_provenance_guard() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237(换图重置:旧封印印记先判定后消失组) -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237(换图重置:旧封印印记先判定后消失组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:237 (换图重置:旧封印印记先判定后消失组) print("--- 轮内刷新 provenance 守卫(刷新图清图不发清图奖励)---") var rows: Array = [] var run := _run_full_clear(20260916, true, rows) @@ -438,8 +438,8 @@ func _smoke_refresh_provenance_guard() -> void: func _smoke_polarity_routing() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:366(极性路由:正向只兑正向、负向只兑负向组) -# oracle: design/gdd/systems/战斗循环/战斗结算.md:366(极性路由:正向只兑正向、负向只兑负向组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:366 (极性路由:正向只兑正向、负向只兑负向组) print("--- 极性路由(挖安全格正向 / 挖危险格负向 / 标中正向 / 标空负向)---") # 战斗随机值面随全局序漂移——构造种子按行为面扫描(谓词见 _polarity_probe_accepts) var probe := {} @@ -577,8 +577,8 @@ func _smoke_polarity_routing() -> void: # 场景 B(②):标危险格 → 直接挖开 → 收轮。 # 场景 C(①③):标安全格 + 标危险格 → 不挖收轮 → 标空/标中判定照常。 func _smoke_seal_irreversible_quadrants() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印不可逆四情况组) -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(封印不可逆四情况组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (封印不可逆四情况组) print("--- 封印不可逆四情况(禁撤封印 + 挖封印格兑付改判)---") # 场景 A:安全格直挖(情况④)+ seal_pull/重复封印拒绝面 var rng_a = load("res://core/rng_service.gd").new() @@ -687,8 +687,8 @@ func _smoke_seal_irreversible_quadrants() -> void: # 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 grant_routed 负向路由(att 归因束形态), # 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。 func _smoke_negative_dictionary() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(下轮行动点修正排队与兑现组) -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(下轮行动点修正排队与兑现组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (下轮行动点修正排队与兑现组) print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 警觉 / action 行动点)---") # 数据面:sapped 行在位(状态表七行 + effect_pool_map 映射在位) var sapped: Dictionary = _db.combat.status_effects.row_by_id("sapped") @@ -782,8 +782,8 @@ func _smoke_negative_dictionary() -> void: # 资源面断言:chain/dig 事件恒携带 resource=true(危险格同权);initial 恒 false(封锁格); # 封锁格查询 = null、危险格未开恒可查。 func _smoke_resource_surface() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(资源面只读查询组) -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235(资源面只读查询组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:235 (资源面只读查询组) print("--- 格面资源资格面(危险格携带 / 封锁格无资源)---") var run1 = _run_battle(1, 0) var run2 = _run_battle(2, 0) diff --git a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd index 934c1aaf..c3394f27 100644 --- a/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd +++ b/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd @@ -419,7 +419,7 @@ var _db func _init() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算接线同种子逐项 golden 对拍组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (结算接线同种子逐项 golden 对拍组) _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) diff --git a/game/tests/unit/battle_settlement/skill_unlock_test.gd b/game/tests/unit/battle_settlement/skill_unlock_test.gd index 53005034..c504f9bd 100644 --- a/game/tests/unit/battle_settlement/skill_unlock_test.gd +++ b/game/tests/unit/battle_settlement/skill_unlock_test.gd @@ -26,6 +26,7 @@ func _init() -> void: # 门谓词:卓尔 (2,9) hp 130(×mult 1.0)→ venom_curb unlock 50 → 阈值 round(130×0.5)=65 func _boss(db, L: int, D: int, kind := "boss"): +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var ec = UnitContainerScript.new(db) var ok: bool = ec.spawn({"level_index": L, "node_depth": D, "hero_hp": int(db.combat.hero.max_hp), "row_kind": kind}) Kit.check("前置:spawn (%d,%d) 受理" % [L, D], ok) @@ -44,7 +45,7 @@ func _has_sid(arr: Array, sid: String) -> bool: func _test_gate_predicate(db) -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(门谓词:条件不满足不进可摇集合组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (门谓词:条件不满足不进可摇集合组) var ec = _boss(db, 2, 9) var u: Dictionary = _boss_u(ec) var mhp: int = int(u.max_hp) @@ -73,7 +74,7 @@ func _test_gate_predicate(db) -> void: # 闩不可逆:解锁后治疗抬回满血 → 复查零新解锁、可摇集合不回退 func _test_latch_irreversible(db) -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(闩型条件首次为真整场保持组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (闩型条件首次为真整场保持组) var ec = _boss(db, 2, 9) var u: Dictionary = _boss_u(ec) ec.apply_damage(u, int(u.max_hp) / 2) # → 半血触发 @@ -85,7 +86,7 @@ func _test_latch_irreversible(db) -> void: # 普通行无字段逐位零扰动:地精 (1,0) 池快照零 unlock 键、过滤恒空转、可摇=全池 func _test_plain_rows_zero_drift(db) -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:301(普攻倍率 1 特例——零漂移组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:301 (普攻倍率 1 特例——零漂移组) var ec = load("res://core/unit_container.gd").new(db) Kit.check("零扰动前置:巨蜘蛛首遇带 spawn(池三行全无 trigger——快照键策略钉死载体)", ec.spawn({"level_index": 2, "node_depth": 1, "hero_hp": int(db.combat.hero.max_hp)})) var u: Dictionary = ec.all_enemies()[0] diff --git a/game/tests/unit/battle_settlement/trigger_cond_test.gd b/game/tests/unit/battle_settlement/trigger_cond_test.gd index fd6347ae..1852c966 100644 --- a/game/tests/unit/battle_settlement/trigger_cond_test.gd +++ b/game/tests/unit/battle_settlement/trigger_cond_test.gd @@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:143(触发条件机制 V1–V10 组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:143 (触发条件机制 V1–V10 组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[trigger_cond_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/battle_settlement/variant_placement_test.gd b/game/tests/unit/battle_settlement/variant_placement_test.gd index b8963d6b..e900d82a 100644 --- a/game/tests/unit/battle_settlement/variant_placement_test.gd +++ b/game/tests/unit/battle_settlement/variant_placement_test.gd @@ -54,7 +54,7 @@ class _CountVariantRng extends RngServiceCore: # 本段经 begin_battle 走完整初始化序后断言:门控一致性 / 布置域 / 数量公式 / 连通片上限 / 确定性。 func _smoke_variant_placement(L: int) -> void: -# oracle: design/gdd/systems/排雷循环/地形变异.md:63(战内布置段门控组) +# oracle: design/gdd/systems/排雷循环/地形变异.md:63 (战内布置段门控组) print("--- 地形变异 布置段(L%d)---" % L) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -148,7 +148,7 @@ func _variant_dump(b) -> String: func _smoke_global_variant() -> void: -# oracle: design/gdd/systems/排雷循环/地形变异.md:74(全局替换制与回落组) +# oracle: design/gdd/systems/排雷循环/地形变异.md:74 (全局替换制与回落组) print("--- 全局作用域变异(门控三维 + 全局池空兜底回落 + 资源面偏斜)---") # ① 跨字段互锁负样本(resource_face ⟺ scope=global 三条 + effect_value 新域 [0,50]) var vt = load("res://data/schemas/variation_table.gd").new() @@ -343,7 +343,7 @@ func _errs_has_cross_field(errs: Array) -> bool: # 全称不可断)。可达判据=①静态(布置面零字面量下界)+②同种子改值产物互异(真实入口=begin_battle 布置段)。 func _test_pack_min_from_table() -> void: -# oracle: design/gdd/systems/排雷循环/地形变异.md:63(连通片格数 [pack_min,pack_max] 读表组) +# oracle: design/gdd/systems/排雷循环/地形变异.md:63 (连通片格数 [pack_min,pack_max] 读表组) # ① 静态:布置面零字面量下界(randi_range(2, 退役——下界读 pack_min 表头单点) var src := FileAccess.get_file_as_string("res://core/board_installer.gd") Kit.check("布置面零字面量下界(randi_range(2, 退役、下界读表单点在位)〔架构整改 V10·静态〕", diff --git a/game/tests/unit/contracts/source_guard_test.gd b/game/tests/unit/contracts/source_guard_test.gd index 86822e4f..4a386b4c 100644 --- a/game/tests/unit/contracts/source_guard_test.gd +++ b/game/tests/unit/contracts/source_guard_test.gd @@ -21,7 +21,7 @@ func _init() -> void: # ---- 类型闸断言(V7——project.godot warning 配置在位〔源码断言〕+审计器脚本在位; # 审计器归零判定走 static_scan S9 闸机,此处兜配置面)---- func _smoke_type_gate() -> void: -# oracle: design/engineering/code-conventions.md:41(封装边界——类型闸源码扫描组) +# oracle: design/engineering/code-conventions.md:41 (封装边界——类型闸源码扫描组) var pj: String = FileAccess.get_file_as_string("res://project.godot") Kit.check("V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕", pj.contains("gdscript/warnings/untyped_declaration=1")) @@ -32,7 +32,7 @@ func _smoke_type_gate() -> void: # ---- 卫生件 grep 闸(V8——死代码/字符串 Callable/影子账残留零命中;源码扫描面)---- func _smoke_hygiene_gates() -> void: -# oracle: design/engineering/code-conventions.md:54(模态覆盖层层级与登记纪律——卫生闸组) +# oracle: design/engineering/code-conventions.md:54 (模态覆盖层层级与登记纪律——卫生闸组) var callables := 0 for d in ["res://core", "res://presentation", "res://data"]: var dir := DirAccess.open(d) diff --git a/game/tests/unit/contracts/system_contracts_test.gd b/game/tests/unit/contracts/system_contracts_test.gd index a5d3cc0b..2a499efc 100644 --- a/game/tests/unit/contracts/system_contracts_test.gd +++ b/game/tests/unit/contracts/system_contracts_test.gd @@ -18,6 +18,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var 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) @@ -40,8 +41,8 @@ func _init() -> void: # ---- B1 兑付配方词表:五配方/三目标规则/双结账政策——常量非空且互异(封闭枚举负空间) ---- func _test_damage_controller_vocabularies() -> void: -# oracle: design/engineering/architecture/architecture.md:89(事件/事实/效果类型/算子四词表封闭——契约词表组) -# oracle: design/engineering/architecture/architecture.md:89(事件/事实/效果类型/算子四词表封闭——契约词表组) +# oracle: design/engineering/architecture/architecture.md:89 (事件/事实/效果类型/算子四词表封闭——契约词表组) +# oracle: design/engineering/architecture/architecture.md:89 (事件/事实/效果类型/算子四词表封闭——契约词表组) var kinds: Array = [ DamageControllerScript.KIND_HERO_ATTACK, DamageControllerScript.KIND_ENEMY_SKILL, DamageControllerScript.KIND_BOARD_TYPED, DamageControllerScript.KIND_BOARD_BLAST, @@ -71,8 +72,8 @@ func _test_damage_controller_vocabularies() -> void: # ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ---- func _test_rng_tag_closure(db) -> void: -# oracle: design/engineering/architecture/architecture.md:90(流 scope 封闭词表组) -# oracle: design/engineering/architecture/architecture.md:90(流 scope 封闭词表组) +# oracle: design/engineering/architecture/architecture.md:90 (流 scope 封闭词表组) +# oracle: design/engineering/architecture/architecture.md:90 (流 scope 封闭词表组) var stream_label: String = String(db.combat.settlement.damage_roll.armor_roll_stream) Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)", RngServiceScript.PURPOSE_TAGS.has(stream_label)) @@ -86,8 +87,8 @@ func _test_rng_tag_closure(db) -> void: # ---- B1→D1 行协议:单据九栏缺省齐备 + 协议公共键形态 ---- func _test_ticket_nine_columns() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(单据九列协议组) -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(单据九列协议组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (单据九列协议组) var ctrl = _bare_controller() var t: Dictionary = ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit", "契约探针", {"channel": "棋盘兑付", "base_value": 1, "target_rule": DamageControllerScript.TARGET_HERO, "ri": 0}) @@ -117,8 +118,8 @@ func _bare_controller(): # ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ---- func _test_status_boolean_acceptance(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:250(受理布尔面组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:250(受理布尔面组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (受理布尔面组) var effects = StatusContainerScript.new(db) # EC1 域闭:notify_battle_start 前授予 = false(受理面封闭) var any_row: Dictionary = {} @@ -145,8 +146,8 @@ func _test_status_boolean_acceptance(db) -> void: # 个体键直传字面量("e1"/"e2"/"e3"——拼写约定单点在 StatusContainer.owner_key_for,测试面模拟授予路径逐只授) func _test_status_individual_isolation(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:251(按个体归属组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:251(按个体归属组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:251 (按个体归属组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -168,8 +169,8 @@ func _test_status_individual_isolation(db) -> void: func _test_status_grant_all_per_unit(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:252(授予即生效组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:252(授予即生效组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:252 (授予即生效组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "mine_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -196,8 +197,8 @@ func _test_status_grant_all_per_unit(db) -> void: func _test_status_death_clears(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:250(战斗结束清空组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:250(战斗结束清空组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:250 (战斗结束清空组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -211,8 +212,8 @@ func _test_status_death_clears(db) -> void: func _test_status_merge_per_unit(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:253(只登记不去重组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:253(只登记不去重组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:253 (只登记不去重组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -235,8 +236,8 @@ func _test_status_merge_per_unit(db) -> void: # ---- B4 结算链契约(断言补齐批——①增益状态结算链):衰减逐值/战斗结束全清/满层拒绝/独立并存/首授快照 ---- func _test_status_decay_and_clear(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:21(先兑付后衰减组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:21(先兑付后衰减组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:21 (先兑付后衰减组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -257,8 +258,8 @@ func _test_status_decay_and_clear(db) -> void: func _test_status_stack_full_reject(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:255(满层拒绝原子且留痕组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:255(满层拒绝原子且留痕组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:255 (满层拒绝原子且留痕组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src := {"source_class": "seal_hit", "origin_cell": Vector2i(-1, -1), "round_index": 0} @@ -276,8 +277,8 @@ func _test_status_stack_full_reject(db) -> void: func _test_status_independent_branch(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:257(合成不设上下限组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:257(合成不设上下限组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:257 (合成不设上下限组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var probe := { @@ -302,8 +303,8 @@ func _test_status_independent_branch(db) -> void: func _test_status_snapshot_first_grant(db) -> void: -# oracle: design/gdd/systems/战斗循环/增益状态.md:258(快照抗重载组) -# oracle: design/gdd/systems/战斗循环/增益状态.md:258(快照抗重载组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组) +# oracle: design/gdd/systems/战斗循环/增益状态.md:258 (快照抗重载组) var effects = StatusContainerScript.new(db) effects.notify_battle_start() var src_first := {"source_class": "seal_hit", "origin_cell": Vector2i(3, 4), "round_index": 1} diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index 623a42e7..ee0eb005 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -59,7 +59,7 @@ func _hits_kind(hits: Array, kind: String) -> Array: # ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态) func _smoke_adventure_api() -> void: -# oracle: design/gdd/systems/冒险循环/冒险结构.md:283(冒险 API 组:开始受理/序列/回调) +# oracle: design/gdd/systems/冒险循环/冒险结构.md:283 (冒险 API 组:开始受理/序列/回调) print("--- 冒险结构 API 闸(重复开始拒绝 · 入口停点 · 放弃两态受理)---") var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(42)) adr.set_hero_override(900, 400) # 首战速胜档(放弃的非战斗受理要走到选路态) @@ -122,7 +122,7 @@ func _api_mark_node(_node: Dictionary, _info: Dictionary, seq: Array) -> void: # ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配冒险成功/种子复现/大奖励/败北线/ # 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃) func _smoke_adventure() -> void: -# oracle: design/gdd/systems/冒险循环/冒险结构.md:284(冒险生命周期组:推进/末关/倒下/重开/生命账) +# oracle: design/gdd/systems/冒险循环/冒险结构.md:284 (冒险生命周期组:推进/末关/倒下/重开/生命账) print("--- 冒险结构验收(三关序列 · 大奖励 · 摘要五件事 · 账本封闭)---") var autoplay = load("res://sim/level_autoplay.gd") # ① 表与覆盖义务(序列关数 = 行集关数;节点源 = 参数档在固定种子下的生成图, @@ -463,7 +463,7 @@ func _smoke_adventure() -> void: # ---- 专长冒烟(feats 四级 · 案D 池接线 · V3 订阅面逐件三列)---- func _smoke_feats() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(奇物授予与凭证保底组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (奇物授予与凭证保底组) print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---") # ① 现行表牌库构成:37 件全量入池——102 数值令牌+37 relic 令牌(四级 rarity 直映射) Kit.check("feat_table 已加载(37 件=基础 27+迁入 2+完美保险 1+迭代 46 换图系七件——换代)", _db.crossrun.feats != null and (_db.crossrun.feats.rows as Array).size() == 37) diff --git a/game/tests/unit/core/auto_player_deduce_test.gd b/game/tests/unit/core/auto_player_deduce_test.gd index f9dfcb0b..47d1a6bf 100644 --- a/game/tests/unit/core/auto_player_deduce_test.gd +++ b/game/tests/unit/core/auto_player_deduce_test.gd @@ -16,7 +16,7 @@ func _init() -> void: func _smoke_zero_chain_rule() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230(0 格连锁传播闭包——模拟玩家算子组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230 (0 格连锁传播闭包——模拟玩家算子组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 Kit.check("前置:表加载零 error", load_ok) diff --git a/game/tests/unit/core/battle_driver_drive_test.gd b/game/tests/unit/core/battle_driver_drive_test.gd index 706aa28e..7df68bd3 100644 --- a/game/tests/unit/core/battle_driver_drive_test.gd +++ b/game/tests/unit/core/battle_driver_drive_test.gd @@ -55,7 +55,7 @@ func _init() -> void: # ① 缺省两参(真编排器):同种子两跑整轮驱动逐位(确定性自证——动作序列快照经 cell_revealed 事件流) func _smoke_drive_round_contract() -> void: -# oracle: design/engineering/architecture/architecture.md:144(效果端口件:收事件交结算取回命中批——驱动循环契约组) +# oracle: design/engineering/architecture/architecture.md:144 (效果端口件:收事件交结算取回命中批——驱动循环契约组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 Kit.check("前置:表加载零 error", load_ok) @@ -81,7 +81,7 @@ func _smoke_drive_round_contract() -> void: # ② provider 形态:策略回调产出 dig/seal_place/refresh 各一——提交与日志回调四元组在案 func _provider_shapes() -> void: -# oracle: design/engineering/architecture/architecture.md:144(端口应答束形状组) +# oracle: design/engineering/architecture/architecture.md:144 (端口应答束形状组) var st := _StubSettlement.new() var script := [ {"kind": "dig", "cell": Vector2i(1, 1)}, {"kind": "seal", "cell": Vector2i(2, 2)}, {"kind": "refresh"}, {"kind": "stop"} ] var logs: Array = [] @@ -102,7 +102,7 @@ func _provider_shapes() -> void: # ③ max_rejects:策略恒返被拒动作——第 3 拒后停手出环(循环次数=3) func _max_rejects() -> void: -# oracle: design/engineering/architecture/architecture.md:144(拒绝通道组) +# oracle: design/engineering/architecture/architecture.md:144 (拒绝通道组) var st := _StubSettlement.new() st.minesweeper.accept = false st.minesweeper.reject_cells = {Vector2i(9, 9): true} @@ -122,7 +122,7 @@ func _max_rejects() -> void: # ④ 合步 next:move 后相位变更即断弃 dig(桩 on_submit 钩子把 phase 推离 Active) func _combined_step_next() -> void: -# oracle: design/engineering/architecture/architecture.md:144(合并步进组) +# oracle: design/engineering/architecture/architecture.md:144 (合并步进组) var st := _StubSettlement.new() st.minesweeper.on_submit = func(action: String) -> void: if action == "move": diff --git a/game/tests/unit/core/board_gen_reward_test.gd b/game/tests/unit/core/board_gen_reward_test.gd index 3f465f05..8e734dab 100644 --- a/game/tests/unit/core/board_gen_reward_test.gd +++ b/game/tests/unit/core/board_gen_reward_test.gd @@ -52,7 +52,7 @@ func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator: # 公式 N×cell ≤ 可用高−12 必须计入 (N−1)×2px 网格间距,否则网格实高溢出被 clip_contents 裁掉; # 现公式 = 计入间距 + 垂直预留 24px〔上 12/下 12——底部不贴 D 区边框〕)---- func _smoke_layout_cell_formula() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75(出生区边长表单点——格边长公式组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长表单点——格边长公式组) print("--- 棋盘布局格边长公式(底部预留回归·四档板型)---") var board_generator = load("res://core/board_generator.gd").new() var board_view = load("res://presentation/views/board_view.gd").new() @@ -82,7 +82,7 @@ func _smoke_layout_cell_formula() -> void: # ---- 地图战斗资源·身份面单元冒烟(格面资源:roll 恰一次 / 雷安同池 / 幂等 / 清图批尾)---- func _smoke_reward_identity_unit() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(格面资源身份——三分出口与资源身份组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (格面资源身份——三分出口与资源身份组) print("--- 地图战斗资源·格面资源身份面 ---") var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(20260911) @@ -144,7 +144,7 @@ class _BoardRowStub: func _smoke_board_generator() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221(确定性复现+布雷纪律组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221 (确定性复现+布雷纪律组) print("--- 棋盘生成 ---") var board_generator = load("res://core/board_generator.gd").new() for L in [1, 2]: @@ -239,7 +239,7 @@ func _board_dump(b) -> String: # ---- 地图战斗资源·伤害量值区间冒烟(roll 域内 + 中点锚 + 变异实际流动 + 定值行不受扰)---- func _smoke_power_interval() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(资源量值区间——伤害量值区间组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (资源量值区间——伤害量值区间组) print("--- 地图战斗资源·伤害量值区间(上阶/下阶随机)---") # ① 表侧静态锚(配置推导——改表不断锚):凡带 power_min/power_max 的行 = 区间行, # 校验配对齐全(lo ≤ hi)+ power = floor 中点(行动经济 条款①期望当量锚);其余行必须零区间字段。 diff --git a/game/tests/unit/core/board_generator_test.gd b/game/tests/unit/core/board_generator_test.gd index 182794f0..698b5d52 100644 --- a/game/tests/unit/core/board_generator_test.gd +++ b/game/tests/unit/core/board_generator_test.gd @@ -40,7 +40,7 @@ func _generate(L: int, D: int, seed0: int) -> BoardData: func _test_generate_shape_and_mines() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224(布雷纪律组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224 (布雷纪律组) var b := _generate(1, 0, 71000) if b == null: Kit.check("生成返回棋盘", false) @@ -61,7 +61,7 @@ func _test_generate_shape_and_mines() -> void: # 目标雷数公式单点(d14):同参同值对拍原式 + 真实产图指标读数同源 func _test_target_mine_count_single_source() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224(雷数单源组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:224 (雷数单源组) var bad := 0 var checked := 0 for L in [1, 2, 3]: @@ -92,7 +92,7 @@ func _test_target_mine_count_single_source() -> void: func _test_digits_equal_neighbor_mine_count() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:223(数字恒真组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:223 (数字恒真组) var b := _generate(1, 1, 72000) if b == null: Kit.check("生成返回棋盘(数字段)", false) @@ -113,7 +113,7 @@ func _test_digits_equal_neighbor_mine_count() -> void: func _test_same_seed_bit_identical() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221(同种子逐位复现组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:221 (同种子逐位复现组) var a := _generate(2, 0, 73000) var b := _generate(2, 0, 73000) if a == null or b == null: @@ -124,7 +124,7 @@ func _test_same_seed_bit_identical() -> void: func _test_clearable_and_inferability_selfconsistent() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:226(可清图与及格线自洽组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:226 (可清图与及格线自洽组) var b := _generate(1, 0, 74000) if b == null: Kit.check("生成返回棋盘(可推理段)", false) @@ -140,7 +140,7 @@ func _test_clearable_and_inferability_selfconsistent() -> void: # ---- V8 出生区边长表单点(架构整改——spawn_edge 表头驱动布雷域基/几何/护栏全导出)---- func _test_spawn_edge_from_table() -> void: -# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75(出生区边长读表组) +# oracle: design/gdd/systems/排雷循环/棋盘生成.md:75 (出生区边长读表组) # ① 纯函数对拍:edge=5 夹具 → target = round(d×(W×H−25))(25 格布雷域基可观测) var p5 := {"w": 12, "h": 12, "density": 0.15, "spawn_edge": 5} Kit.check("V8 纯函数:edge=5 布雷域基 25 格(target=round(d×(144−25)))〔架构整改 V8〕", diff --git a/game/tests/unit/core/breakpoint_resume_test.gd b/game/tests/unit/core/breakpoint_resume_test.gd index c68200b9..24b28c4a 100644 --- a/game/tests/unit/core/breakpoint_resume_test.gd +++ b/game/tests/unit/core/breakpoint_resume_test.gd @@ -15,7 +15,7 @@ var _bp_path := "user://test_breakpoint_resume.json" func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180(断点域契约:七字段往返/续玩确定性/幂等/三结局清除组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:180 (断点域契约:七字段往返/续玩确定性/幂等/三结局清除组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[breakpoint_resume_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/config_db_cross_validator_test.gd b/game/tests/unit/core/config_db_cross_validator_test.gd index 0b82f07d..8cb7ee18 100644 --- a/game/tests/unit/core/config_db_cross_validator_test.gd +++ b/game/tests/unit/core/config_db_cross_validator_test.gd @@ -13,7 +13,7 @@ const StatusTableScript := preload("res://data/schemas/status_effect_table.gd") func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(跨表校验宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (跨表校验宿主——装载组) _test_all_links_valid() _test_dangling_skill_id() _test_cast_side_mismatch() @@ -47,7 +47,7 @@ func _cats(errs: Array) -> Array[String]: # ---- 正向:技能池全对齿(普攻恰一行、buff 挂敌方侧)应零错,防校验误伤 ---- func _test_all_links_valid() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(悬空引用类别:跨表链接全有效组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:跨表链接全有效组) var skills := _skill([ {"skill_id": "attack", "type": "basic"}, {"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_strong"}, @@ -63,7 +63,7 @@ func _test_all_links_valid() -> void: # ---- 负向:技能池引用表里不存在的 skill_id 必须报 ---- func _test_dangling_skill_id() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(悬空引用类别:skill_id 悬空组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (悬空引用类别:skill_id 悬空组) var skills := _skill([{"skill_id": "attack", "type": "basic"}]) var enemies := _enemy([ {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [ @@ -76,7 +76,7 @@ func _test_dangling_skill_id() -> void: # ---- 负向:buff 挂到主角侧状态(side 反向)必须报 ---- func _test_cast_side_mismatch() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(side 反向校验组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (side 反向校验组) var skills := _skill([{"skill_id": "raise_morale", "type": "buff", "buff_ref": "st_hero_side"}]) var enemies := _enemy([ {"level_index": 1, "row_kind": "battle", "depth_band": 0, "enemy_id": "goblin", "pool": [ diff --git a/game/tests/unit/core/config_db_resolver_test.gd b/game/tests/unit/core/config_db_resolver_test.gd index 40626662..9e5e4c53 100644 --- a/game/tests/unit/core/config_db_resolver_test.gd +++ b/game/tests/unit/core/config_db_resolver_test.gd @@ -12,7 +12,7 @@ var _resolver: ConfigDbResolver func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(解析器宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (解析器宿主——装载组) _resolver = ResolverScript.new( func(level, category, message) -> void: # 诊断通道签名按调用实参收(首参为表 id 字符串) _diags.append("%s/%s/%s" % [str(level), str(category), str(message)]), @@ -55,7 +55,7 @@ func _difficulty_table(): func _test_band_hit() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(精确命中零警告组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (精确命中零警告组) 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", "")) == "giant_spider") @@ -65,7 +65,7 @@ func _test_band_hit() -> void: func _test_rowset_order_preserved() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(行集寻址与行序保序组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (行集寻址与行序保序组) var rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle") var ids: Array[String] = [] for r in rows: @@ -75,7 +75,7 @@ func _test_rowset_order_preserved() -> void: func _test_top_band_clamp() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(超最大行走封顶取最大行组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (超最大行走封顶取最大行组) _diags.clear() var rows: Array = _resolver.resolve_enemy_rowset(1, 99, "battle") Kit.check("越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕", @@ -91,7 +91,7 @@ func _test_top_band_clamp() -> void: func _test_row_kind_routing() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(行类别路由组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (行类别路由组) var battle_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "battle") var boss_rows: Array = _resolver.resolve_enemy_rowset(1, 0, "boss") Kit.check("行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕", @@ -100,7 +100,7 @@ func _test_row_kind_routing() -> void: func _test_request_oob() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:203(越域致命中止组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:203 (越域致命中止组) _diags.clear() _resolver.resolve_enemy_rowset(0, 0, "battle") Kit.check("关卡序号 <1 → fatal request_oob〔fv5〕", _has_fatal_oob()) @@ -120,7 +120,7 @@ func _has_fatal_oob() -> bool: func _test_difficulty_clamp() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:202(难度 clamp 组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 (难度 clamp 组) var m: Dictionary = _resolver.resolve_difficulty_modifiers(1, 0) Kit.check("难度乘数越域被 clamp:hp 99 → 上限 10", absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001) Kit.check("难度乘数越域被 clamp:atk 0.01 → 下限 0.1", absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001) diff --git a/game/tests/unit/core/curio_refresh_paid_test.gd b/game/tests/unit/core/curio_refresh_paid_test.gd index 94e2a06f..6be57be7 100644 --- a/game/tests/unit/core/curio_refresh_paid_test.gd +++ b/game/tests/unit/core/curio_refresh_paid_test.gd @@ -8,7 +8,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/engineering/architecture/architecture.md:107(CurioDeck:三选一/商店共享池、抽签、cap 库存——刷新付费组) +# oracle: design/engineering/architecture/architecture.md:107 (CurioDeck:三选一/商店共享池、抽签、cap 库存——刷新付费组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[curio_refresh_paid_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/effect_words_t1_test.gd b/game/tests/unit/core/effect_words_t1_test.gd index 265ff860..c9902171 100644 --- a/game/tests/unit/core/effect_words_t1_test.gd +++ b/game/tests/unit/core/effect_words_t1_test.gd @@ -11,7 +11,7 @@ const EffectEngineScript = preload("res://core/effect_engine.gd") func _init() -> void: -# oracle: design/engineering/architecture/architecture.md:89(效果词表封闭——扩签封闭性组) +# oracle: design/engineering/architecture/architecture.md:89 (效果词表封闭——扩签封闭性组) # ① 词表在册断言(扩签面:2 事件+8 事实+7 效果类型+1 算子) Kit.check_quiet(EffectEngineScript.EVENTS.has("dig_stop") and EffectEngineScript.EVENTS.has("battle_won"), "新事件在册:dig_stop/battle_won〔专长批 V1〕") diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd b/game/tests/unit/core/encapsulation_p2_sweep_test.gd index 3e55fad3..08bd3e76 100644 --- a/game/tests/unit/core/encapsulation_p2_sweep_test.gd +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd @@ -11,34 +11,34 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/engineering/code-conventions.md:41(封装边界:跨对象往来只走公开方法与属性——P2 词表单点扫描组) +# oracle: design/engineering/code-conventions.md:41 (封装边界:跨对象往来只走公开方法与属性——P2 词表单点扫描组) _sweep_generator() _sweep_production_face() quit(Kit.finish("encapsulation_p2_sweep_test")) func _src(path: String) -> String: -# oracle: design/engineering/code-conventions.md:41(源码采样组) +# oracle: design/engineering/code-conventions.md:41 (源码采样组) 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: -# oracle: design/engineering/code-conventions.md:41(零命中扫描组) +# oracle: design/engineering/code-conventions.md:41 (零命中扫描组) 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: -# oracle: design/engineering/code-conventions.md:41(正锚存在组) +# oracle: design/engineering/code-conventions.md:41 (正锚存在组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 正锚在案〔架构P2〕" % [label, pattern], re.search(src) != null) # ① V1 生成器词表定义删除与改指 func _sweep_generator() -> void: -# oracle: design/engineering/code-conventions.md:41(生成器本地副本归零组) +# oracle: design/engineering/code-conventions.md:41 (生成器本地副本归零组) var src := _src("res://core/node_map_generator.gd") if src.length() == 0: return @@ -48,7 +48,7 @@ func _sweep_generator() -> void: # ② V2 生产三目录定义唯一性(递归扫 .gd,恰好 1 处且落 level_table.gd) func _sweep_production_face() -> void: -# oracle: design/engineering/code-conventions.md:41(schema 唯一定点组) +# oracle: design/engineering/code-conventions.md:41 (schema 唯一定点组) var files: Array[String] = [] for dir_path in ["res://core", "res://data", "res://presentation"]: _collect_gd(dir_path, files) diff --git a/game/tests/unit/core/encapsulation_sweep_test.gd b/game/tests/unit/core/encapsulation_sweep_test.gd index 06af956c..e1bbf105 100644 --- a/game/tests/unit/core/encapsulation_sweep_test.gd +++ b/game/tests/unit/core/encapsulation_sweep_test.gd @@ -14,7 +14,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/engineering/code-conventions.md:41(封装边界——五处结构性零命中扫描组) +# oracle: design/engineering/code-conventions.md:41 (封装边界——五处结构性零命中扫描组) _sweep_settlement_segments() _sweep_orchestrator_methods() _sweep_breakpoint_pair() @@ -26,7 +26,7 @@ func _init() -> void: # 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) func _src(path: String) -> String: -# oracle: design/engineering/code-conventions.md:41(源码采样组) +# oracle: design/engineering/code-conventions.md:41 (源码采样组) var src := FileAccess.get_file_as_string(path) Kit.check("扫描面非空前置:%s" % path, src.length() > 0) return src @@ -34,21 +34,21 @@ func _src(path: String) -> String: # 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位) func _zero(pattern: String, src: String, label: String, path: String) -> void: -# oracle: design/engineering/code-conventions.md:41(零命中扫描组) +# oracle: design/engineering/code-conventions.md:41 (零命中扫描组) 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: -# oracle: design/engineering/code-conventions.md:41(正锚存在组) +# oracle: design/engineering/code-conventions.md:41 (正锚存在组) 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: -# oracle: design/engineering/code-conventions.md:41(结算段封装扫描组) +# oracle: design/engineering/code-conventions.md:41 (结算段封装扫描组) var classes := { "res://core/settlement/grant_segment.gd": "GrantSegment", "res://core/settlement/combat_segment.gd": "CombatSegment", @@ -68,7 +68,7 @@ func _sweep_settlement_segments() -> void: # ② V3 编排器搬家五方法零残留 func _sweep_orchestrator_methods() -> void: -# oracle: design/engineering/code-conventions.md:41(编排器方法面扫描组) +# oracle: design/engineering/code-conventions.md:41 (编排器方法面扫描组) var src := _src("res://core/battle_orchestrator.gd") if src.length() == 0: return @@ -78,7 +78,7 @@ func _sweep_orchestrator_methods() -> void: # ③ V4 断点域(adventure_runner → BreakpointStore 公开面) func _sweep_breakpoint_pair() -> void: -# oracle: design/engineering/code-conventions.md:41(断点对封装扫描组) +# oracle: design/engineering/code-conventions.md:41 (断点对封装扫描组) var src := _src("res://core/adventure_runner.gd") if src.length() == 0: return @@ -88,7 +88,7 @@ func _sweep_breakpoint_pair() -> void: # ④ V5 存档双向公开面(save_manager↔slot_store) func _sweep_save_pair() -> void: -# oracle: design/engineering/code-conventions.md:41(存档对封装扫描组) +# oracle: design/engineering/code-conventions.md:41 (存档对封装扫描组) 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") @@ -102,7 +102,7 @@ func _sweep_save_pair() -> void: # ⑤ V6 效果宿主累积面(grant → BattleFxHost 公开累积方法) func _sweep_fx_host_accumulators() -> void: -# oracle: design/engineering/code-conventions.md:41(特效宿主累积面扫描组) +# oracle: design/engineering/code-conventions.md:41 (特效宿主累积面扫描组) var src := _src("res://core/settlement/grant_segment.gd") if src.length() == 0: return @@ -112,7 +112,7 @@ func _sweep_fx_host_accumulators() -> void: # ⑥ V7 配置挂载面(config_db → 三 schema bind_resolver) func _sweep_config_resolver() -> void: -# oracle: design/engineering/code-conventions.md:41(配置挂载面扫描组) +# oracle: design/engineering/code-conventions.md:41 (配置挂载面扫描组) var src := _src("res://core/config_db.gd") if src.length() == 0: return diff --git a/game/tests/unit/core/feat_vouchers_test.gd b/game/tests/unit/core/feat_vouchers_test.gd index cda7db0d..ad9b7b65 100644 --- a/game/tests/unit/core/feat_vouchers_test.gd +++ b/game/tests/unit/core/feat_vouchers_test.gd @@ -16,7 +16,7 @@ func EffectEngineKindProbe(_db) -> bool: func _init() -> void: -# oracle: design/gdd/systems/冒险循环/奇物.md:465(奇物凭证五件与留任二件组) +# oracle: design/gdd/systems/冒险循环/奇物.md:465 (奇物凭证五件与留任二件组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[feat_vouchers_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/hero_ledger_test.gd b/game/tests/unit/core/hero_ledger_test.gd index e8e30b4c..ad786cfa 100644 --- a/game/tests/unit/core/hero_ledger_test.gd +++ b/game/tests/unit/core/hero_ledger_test.gd @@ -14,7 +14,7 @@ const LevelRunnerScript := preload("res://core/level_runner.gd") func _init() -> void: -# oracle: design/gdd/systems/冒险循环/冒险结构.md:288(生命一致性:三写入口封顶 [0,生效上限]——主角账组) +# oracle: design/gdd/systems/冒险循环/冒险结构.md:288 (生命一致性:三写入口封顶 [0,生效上限]——主角账组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[hero_ledger_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/level_map_type_vis_test.gd b/game/tests/unit/core/level_map_type_vis_test.gd index f97fdd19..9cb4d92d 100644 --- a/game/tests/unit/core/level_map_type_vis_test.gd +++ b/game/tests/unit/core/level_map_type_vis_test.gd @@ -10,7 +10,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd") func _init() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(信息迷雾类型可见性宿主——装载组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (信息迷雾类型可见性宿主——装载组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "可见性用例:数值表加载成功") @@ -103,7 +103,7 @@ func _runner(db, nodes: Array, anchor_id: int, candidates: Array, n: int = 2): # ---- ⑥ N 进数据表(含越域校验)〔fv6〕---- func _test_field_in_table(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(迷雾参数读表——字段在表组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (迷雾参数读表——字段在表组) var n_in_table: int = int(db.run.level.info_visible_layers) Kit.check("N 落表:level.tres 读数 2(实际 %d)〔fv6〕" % n_in_table, n_in_table == 2) var t := LevelTableScript.new() @@ -127,7 +127,7 @@ func _has_n_domain_err(errs: Array) -> bool: # ---- ① 邻近 N 层见类型(层距边界)〔fv1〕---- func _test_layer_distance(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(层距 N 可见性组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (层距 N 可见性组) var nodes := _graph_nodes() var r = _runner(db, nodes, 1, [nodes[2]]) # 锚 = 节点 1(层 1),候选 = 节点 2 var vis: Array = r.type_visible_ids() @@ -139,7 +139,7 @@ func _test_layer_distance(db) -> void: # ---- ② 下一跳恒可见(跨层跳边候选)〔fv4〕---- func _test_candidate_exempt(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(候选豁免组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (候选豁免组) var nodes := _graph_nodes() var r = _runner(db, nodes, 6, [nodes[3]]) # 锚 = 节点 6(层 5),候选 = 节点 3(层 2,层距 3) var vis: Array = r.type_visible_ids() @@ -150,7 +150,7 @@ func _test_candidate_exempt(db) -> void: # ---- ③ Boss/终点不入雾+⑤ 入口停点锚层 0〔fv3〕〔fv1〕---- func _test_boss_exempt(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(Boss 豁免组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (Boss 豁免组) var nodes := _graph_nodes() var r = _runner(db, nodes, -1, [nodes[0]]) # 入口停点(无当前节点,锚层 0) var vis: Array = r.type_visible_ids() @@ -163,7 +163,7 @@ func _test_boss_exempt(db) -> void: # ---- ④ 单圈回雾(visited 不豁免)〔fv7〕---- func _test_single_circle(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(单圈可见组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单圈可见组) var nodes := _graph_nodes() var r = _runner(db, nodes, 6, [nodes[7], nodes[3]]) r.visited.assign([nodes[0], nodes[1], nodes[2], nodes[3], nodes[4], nodes[5], nodes[6]]) @@ -176,7 +176,7 @@ func _test_single_circle(db) -> void: # ---- ⑤ 单链退化形态(出度 ≤1 图,编号 = id 句柄)〔fv1〕---- func _test_single_chain_entry(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(单链入口组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (单链入口组) var nodes := _chain_nodes() var r = _runner(db, nodes, -1, [nodes[0]]) var vis: Array = r.type_visible_ids() @@ -189,7 +189,7 @@ func _test_single_chain_entry(db) -> void: # ---- ⑥ N 读表:可见窗随表值随动〔fv6〕---- func _test_n_from_table(db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176(N 读表组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:176 (N 读表组) var nodes := _graph_nodes() var r = _runner(db, nodes, 1, [nodes[2]]) # 锚层 1 db.run.level.info_visible_layers = 1 @@ -207,6 +207,7 @@ func _test_n_from_table(db) -> void: # ---- ⑦ 续玩重建同值〔fv7〕---- func _test_resume_rebuild(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var t := LevelTableScript.new() t.info_visible_layers = 2 t.rows = [{"level_index": 1, "display_name": "夹具关", "rest_heal_pct": 30}] diff --git a/game/tests/unit/core/level_shop_economy_test.gd b/game/tests/unit/core/level_shop_economy_test.gd index 7fafb4c9..6d7eb724 100644 --- a/game/tests/unit/core/level_shop_economy_test.gd +++ b/game/tests/unit/core/level_shop_economy_test.gd @@ -232,6 +232,7 @@ func _board_row_exact(level_index: int, depth: int) -> bool: func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2/G3/G4/G5 同款:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -261,7 +262,7 @@ func _init() -> void: # ---- 数值入表等价证明(d11/d16a——原字面量迁移:表值 == 迁移前字面量,消费端读表)---- func _smoke_table_migration() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:195(行集纪律——表迁移组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:195 (行集纪律——表迁移组) print("--- 数值入表(shop 两 knob + hero 显示名 · 原值迁移)---") Kit.check("shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕", _db.run.shop.relic_base_price == 15.0) Kit.check("hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕", _db.combat.hero.display_name == "探险者") @@ -276,7 +277,7 @@ func _smoke_table_migration() -> void: # 敌侧/板侧解析一律走 ConfigDB 真实消费链,禁直读行集自算)---- func _smoke_generated_coverage() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(每终点前恰一店——生成覆盖扫描组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (每终点前恰一店——生成覆盖扫描组) var enemy_ok := true var board_ok := true var boss_band_ok := true @@ -327,7 +328,7 @@ func _smoke_generated_coverage() -> void: # ---- 关卡节点图·生成图推进冒烟(图 = 该关参数档 × 种子)---- func _smoke_level() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(关卡图校验推进组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡图校验推进组) print("--- 关卡节点图·生成图推进(参数档×种子产图·满配冒险成功/恢复公式/败北线/回放确定性)---") var autoplay = load("res://sim/level_autoplay.gd") var hero_base := {"atk": int(_db.combat.hero.atk), "hp": int(_db.combat.hero.max_hp), "ac": int(_db.combat.hero.armor_class)} # 配置基线快照——⑤ 回基线对比用(改表自动跟随,不再硬编码) @@ -441,7 +442,7 @@ func _drive_to_rest(level_index: int, seed_val: int, gap: int) -> Dictionary: # ---- 分支节点图冒烟:图合法性校验 + L2 幽暗地域生成图行进(图 = 参数档 × 种子)---- func _smoke_level_graph() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(图结构与行进纪律组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (图结构与行进纪律组) print("--- 关卡节点图·分支节点图(L2 幽暗地域·生成图)---") var autoplay = load("res://sim/level_autoplay.gd") # ① 关卡表行级校验零错误(关卡表只承载序号/显示名/恢复比例——图合法性由 validate_graph 在起关时承担) @@ -799,7 +800,7 @@ func _fresh_rng(seed_val: int): # ---- ① GP 冒险钱包·到账 ---- func _smoke_gp_wallet() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(GP 钱包入账组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP 钱包入账组) print("--- GP 冒险钱包·到账(战斗完成入账恰一次 + Boss 加值)---") var autoplay = load("res://sim/level_autoplay.gd") # ① L1 首战(普通战档——掷定 [2,4] + 关级加值 0):runner 驱动,收集 battle_finished 与钱包事件 @@ -865,7 +866,7 @@ func _escrow_sum_of(rows: Array) -> int: # ---- ② escrow 隔离(GP安全格兑付 / 破财直验 / 钱包 clamp)---- func _smoke_gp_escrow() -> void: -# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233(GP escrow 组) +# oracle: design/gdd/systems/战斗循环/地图战斗资源.md:233 (GP escrow 组) print("--- GP escrow 隔离(GP安全格兑付入缓冲 · 破财不透支)---") # ① 种子扫描:找GP 袋(gp 型安全格)可挖掘局——挖开兑付 +3 入 escrow,战斗末随 gp_dropped 一次上抛 var scan := {} @@ -979,7 +980,7 @@ func _find_gp_dig(minesweeper, settlement) -> Dictionary: # ---- ③ 商店房三收口 ---- func _smoke_shop_room() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(商店房呈现组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (商店房呈现组) print("--- 商店房三收口(现价/加算区/余额不足拒/离开回选路/收束清零)---") var autoplay = load("res://sim/level_autoplay.gd") # 行进到商店房(按图型别定位商店节点——候选取最小句柄行进,到达即停): @@ -1113,7 +1114,7 @@ func _shop_products_by_id(prods: Array) -> Dictionary: # ---- ④ 商店编排性质(生成图扫描面——三条性质 + 终点跨表解析)---- func _smoke_shop_graph() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(必经恰一店图纪律组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (必经恰一店图纪律组) print("--- 商店编排性质(每终点前一位恰一店 · 店数=终点数 · 全路径恰穿一店)---") var pred_bad := 0 var term_total := 0 @@ -1184,7 +1185,7 @@ func _smoke_shop_graph() -> void: # ---- ⑥ 大岔口分区域兑付(V3——多终点图走哪条路决定打谁,任一 Boss 胜即收束恰一次)---- func _smoke_multi_terminal_settlement() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:185(多终局收束组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:185 (多终局收束组) print("--- 大岔口分区域兑付(L2 两轴各打各的终点)---") var autoplay = load("res://sim/level_autoplay.gd") var nodes: Array = _generated_graph(2, 42).get("nodes", []) @@ -1252,7 +1253,7 @@ func _paths_all_one_shop(nodes: Array) -> bool: # ---- ⑤ 商店共享池(三选一同构/乘法价/跨渠道共账/切边/断点扩键)---- func _smoke_shop_pool() -> void: -# oracle: design/engineering/architecture/architecture.md:107(商店共享池组) +# oracle: design/engineering/architecture/architecture.md:107 (商店共享池组) print("--- 商店共享池(抽3买1全重抽 · 乘法定价 ceil · 池空硬闸 · 切边零入店)---") var autoplay = load("res://sim/level_autoplay.gd") var shelf_kinds: Array = [] @@ -1415,7 +1416,7 @@ func _smoke_shop_pool() -> void: # ---- 商店房 node_entered 载荷(尾波 V1——_enter_node pool/legacy 双发射路径归一后的行为保持断言)---- func _test_shop_node_entered_payload() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184(行进至此打开商店覆盖层——载荷组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:184 (行进至此打开商店覆盖层——载荷组) print("--- 商店房 node_entered 载荷(pool 含 shelf:true+gp 现值/legacy 无 shelf 键)---") var autoplay = load("res://sim/level_autoplay.gd") # pool 态:载荷字典经信号闭包当场取样(字典为引用累积器——lambda 按值捕获标量的绕行惯例) @@ -1574,7 +1575,7 @@ func _has_err_category(errs: Array, category: String) -> bool: # ---- 货架位数读表(架构整改——shelf_size 表头单点:进店与重抽同源)---- func _test_shelf_size_from_table() -> void: -# oracle: design/gdd/ux/shop.md:13(货架位数读表组) +# oracle: design/gdd/ux/shop.md:13 (货架位数读表组) var autoplay = load("res://sim/level_autoplay.gd") var st = _db.run.shop var saved: int = int(st.shelf_size) @@ -1596,7 +1597,7 @@ func _test_shelf_size_from_table() -> void: # ---- 散折价收回规则键(架构整改 R2——shop.spend_discount 表字段退役,折价读 economy_spend.discount_pct)---- func _test_spend_discount_from_rule_key() -> void: -# oracle: design/gdd/systems/排雷循环/行动经济.md:256(换图基座:挖格产 RAP 与刷新折扣组) +# oracle: design/gdd/systems/排雷循环/行动经济.md:256 (换图基座:挖格产 RAP 与刷新折扣组) # 字段真删:shop.tres 加载零诊断 + spend_discount 属性不在位 Kit.check("shop.tres 加载零诊断(spend_discount 字段真删——schema 与表行同卸)〔架构整改 V2〕", _db.diagnostics_count("error") == 0 and _db.run.shop.get("spend_discount") == null) diff --git a/game/tests/unit/core/level_table_validate_test.gd b/game/tests/unit/core/level_table_validate_test.gd index b845d220..14201d91 100644 --- a/game/tests/unit/core/level_table_validate_test.gd +++ b/game/tests/unit/core/level_table_validate_test.gd @@ -11,7 +11,7 @@ const LevelTableScript := preload("res://data/schemas/level_table.gd") func _init() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(关卡表消费侧校验宿主) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (关卡表消费侧校验宿主) _test_positive() _test_negative() _test_scope_not_extended() @@ -38,7 +38,7 @@ func _has(errs: Array, cat: String, needle: String = "") -> bool: # ---- 正例(四检全过)---- func _test_positive() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(良构基底正向组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (良构基底正向组) # 多入口多终点:两入口共汇入一岔口,两终点各在互异深度 var multi := [ _n(0, "battle", 0, [2]), _n(1, "battle", 0, [2]), @@ -61,7 +61,7 @@ func _test_positive() -> void: # ---- 负例九类(各报对应类别与判据消息)---- func _test_negative() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(违规配置加载期致命——负向组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (违规配置加载期致命——负向组) var non_boss_end := [_n(0, "battle", 0, [1]), _n(1, "battle", 1, [])] Kit.check("负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕", _has(_errs(non_boss_end), "structural", "终点 1 非 Boss")) @@ -95,7 +95,7 @@ func _test_negative() -> void: # ---- 「勿扩勿缩」反证:本检不含商店位与个数对参数档的核对 ---- func _test_scope_not_extended() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(校验范围不外扩组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (校验范围不外扩组) # 合法形状但全程无商店/三个终点同入口图——四检不该因此报错(商店位归冒烟断言、个数归产出侧) var no_shop := [_n(0, "battle", 0, [1, 2]), _n(1, "battle", 1, [3]), _n(2, "rest", 1, [3]), _n(3, "boss", 2, [])] Kit.check("范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕", _errs(no_shop).is_empty()) diff --git a/game/tests/unit/core/meta_progression_forge_test.gd b/game/tests/unit/core/meta_progression_forge_test.gd index 1e4af588..35c769f6 100644 --- a/game/tests/unit/core/meta_progression_forge_test.gd +++ b/game/tests/unit/core/meta_progression_forge_test.gd @@ -133,7 +133,7 @@ func _auto_play_round(settlement, out: Dictionary) -> void: # ---- 元进度 ②树购买面(购买受理/持久/余额不足拒绝/未知节点拒绝; # 行价=表值 flat 单价)---- func _meta_pricing_and_buy() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:198(单币可判:购买只扣钥石、余额不足拒绝零变化组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:198 (单币可判:购买只扣钥石、余额不足拒绝零变化组) print("--- 元进度·树购买面(隔离档 smoke_meta_buy/dry)---") _meta_smoke_cleanup() var sv = _meta_new_save(_META_SMOKE_PATHS[0]) @@ -160,7 +160,7 @@ func _meta_pricing_and_buy() -> void: # ---- 元进度 ⑥Boss 两档 ⑦钥石不丢 ⑧工坊解锁 ⑩摘要副本与 cap(合成摘要驱动——settle 纯账面零跑局)---- func _meta_settle_and_workshop() -> void: -# oracle: design/gdd/systems/跨局循环/元进度.md:150(钥石收束入账不丢组) +# oracle: design/gdd/systems/跨局循环/元进度.md:150 (钥石收束入账不丢组) print("--- 元进度·收束入账与工坊(隔离档 smoke_meta_settle/workshop/cap)---") var sv = _meta_new_save(_META_SMOKE_PATHS[2]) var mp = load("res://core/meta_progression.gd").new(_db, sv) @@ -353,7 +353,7 @@ func _meta_settle_and_workshop() -> void: # ---- 元进度 ④注入可见 + 白板成立(验收 4/2/3——程序面;MC 逐位回归归 T7 全量验证)---- func _meta_injection() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:201(注入可判:开局覆盖值分层可加组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:201 (注入可判:开局覆盖值分层可加组) print("--- 元进度·注入可见与白板(隔离档 smoke_meta_white/dry)---") var sv = _meta_new_save(_META_SMOKE_PATHS[1]) var mp = load("res://core/meta_progression.gd").new(_db, sv) @@ -397,7 +397,7 @@ func _meta_injection() -> void: # ---- 锻造工坊·两根类型轴所有权移交(V5 前半:hero.tres 两轴删除+锻造配置默认值)---- # 后半(build_adventure_params 注入默认值/选中覆盖)随 T4 注入接线同批断言;写口/解锁面归 T3。 func _smoke_forge_axes() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220(锻造两轴单选注入与树零攻防组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 (锻造两轴单选注入与树零攻防组) print("--- 锻造工坊·类型轴所有权移交(T2)---") # V5:hero.tres 静态表两轴已删(全表加载零违例——config_db_test 同款闸;此处钉字段不在位) Kit.check("hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕", @@ -560,7 +560,7 @@ func _smoke_forge_axes() -> void: # ---- 天赋树冒烟(v0.8 12 节点:schema/购买面/Fork 一节点双态/彩蛋特化)---- func _smoke_tree() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:194(天赋树购买与六键合成组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (天赋树购买与六键合成组) print("--- 天赋树(12 节点 · 购买面 · Fork 双态 · 彩蛋觉醒)---") # 〔V1〕结构:12 行 + schema 零违例 Kit.check("meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕", @@ -643,7 +643,7 @@ func _smoke_tree() -> void: # ---- 天赋树六键行为冒烟(V10:开局姿态窗/危险直觉限次/财富之道聚散/守侧失误补偿; # 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))---- func _smoke_tree_rules() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:194(规则键参数读表组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (规则键参数读表组) print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---") # 开局姿态组:每关首轮窗(battle_index 事实——引擎直构 + 自建 ctx) var fx = load("res://core/effect_engine.gd").new(null) @@ -784,7 +784,7 @@ func _smoke_tree_rules() -> void: # ---- 锻造契约补齐(断言补齐批——⑥锻造工坊):拒购零副作用/祝福件零泄漏/默认值解耦/甲型双轴 ---- func _test_forge_buy_reject() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:222(钥石不足购买被拒、余额与等级零变化组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:222 (钥石不足购买被拒、余额与等级零变化组) var sv = _meta_new_save("user://smoke_meta_reject.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) sv.meta_state._add_keystone(10) # 低于首购价 20——余额不足面 @@ -799,7 +799,7 @@ func _test_forge_buy_reject() -> void: func _test_blessing_no_forge_leak() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220(树淬炼对攻击与 AC 零贡献组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 (树淬炼对攻击与 AC 零贡献组) var sv = _meta_new_save("user://smoke_meta_blessing.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) var fs0: Dictionary = sv.meta_state.forge_state().duplicate(true) @@ -818,7 +818,7 @@ func _test_blessing_no_forge_leak() -> void: func _test_default_axes_pre_key() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:217(未点亮期间默认值照常注入组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:217 (未点亮期间默认值照常注入组) var sv = _meta_new_save("user://smoke_meta_axesw.json") var mp = load("res://core/meta_progression.gd").new(_db, sv) var ax: Dictionary = mp.hero_type_axes() @@ -827,7 +827,7 @@ func _test_default_axes_pre_key() -> void: func _test_armor_switch_dual_axes() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:221(换装不丢级不生效——双轴组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:221 (换装不丢级不生效——双轴组) var sv = _meta_new_save("user://smoke_meta_axesw.json") sv.meta_state._add_keystone(1000) var mp = load("res://core/meta_progression.gd").new(_db, sv) diff --git a/game/tests/unit/core/minesweeper_rules_test.gd b/game/tests/unit/core/minesweeper_rules_test.gd index cb3b50ba..c6d52fd4 100644 --- a/game/tests/unit/core/minesweeper_rules_test.gd +++ b/game/tests/unit/core/minesweeper_rules_test.gd @@ -17,7 +17,7 @@ var _ae: ActionEconomy func _init() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:224(排雷规则宿主——装载组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:224 (排雷规则宿主——装载组) _db = load("res://core/config_db.gd").new() as ConfigDb if not _db.load_tables("res://data/tables", false): Kit.check("数值表加载", false) @@ -46,7 +46,7 @@ func _init() -> void: func _test_install_invariants() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:228(装板四不变式组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:228 (装板四不变式组) _rules.install_board(_board) Kit.check("装板后 current_board 即所装棋盘", _rules.current_board() == _board) var opened: int = _rules.revealed_cells().size() @@ -70,7 +70,7 @@ func _unrevealed_safe() -> Vector2i: func _test_dig_face_rules() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(挖/封印合法性与可辨拒绝组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (挖/封印合法性与可辨拒绝组) var revealed_first = _rules.revealed_cells()[0] as Vector2i var out: Array = _rules.settle_batch([{"action": "dig", "cell": revealed_first}]) var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {} @@ -85,7 +85,7 @@ func _test_dig_face_rules() -> void: func _test_reveal_advances() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230(揭示推进与连锁组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:230 (揭示推进与连锁组) var c := _unrevealed_safe() if c == Vector2i(-1, -1): Kit.check("存在未揭示安全格(揭示推进前置)", false) @@ -99,7 +99,7 @@ func _test_reveal_advances() -> void: func _test_seal_place() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231(重复封印拒绝组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:231 (重复封印拒绝组) var c := _unrevealed_safe() if c == Vector2i(-1, -1): Kit.check("存在未揭示安全格(封印前置)", false) @@ -112,7 +112,7 @@ func _test_seal_place() -> void: func _test_reject_out_of_catalog() -> void: -# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236(目录外拒绝组) +# oracle: design/gdd/systems/排雷循环/排雷玩法.md:236 (目录外拒绝组) var out: Array = _rules.settle_batch([{"action": "not_a_real_action", "cell": Vector2i(0, 0)}]) var o: Dictionary = (out[0] as Dictionary) if not out.is_empty() else {} Kit.check("目录外意图被拒(不静默吞掉)——实际载荷 %s" % str(o).substr(0, 80), @@ -122,7 +122,7 @@ func _test_reject_out_of_catalog() -> void: # ---- V14 棋盘揭示预览只读口(架构整改——preview_reveal 与实挖揭示集同口径、零状态写入零事件)---- func _test_preview_reveal_readonly() -> void: -# oracle: design/engineering/architecture/architecture.md:103(preview_reveal 只读预览口——零写入零事件组) +# oracle: design/engineering/architecture/architecture.md:103 (preview_reveal 只读预览口——零写入零事件组) var events := 0 _rules.cell_revealed.connect(func(_evt): events += 1) # 零发射哨兵(preview 全程不触发揭示事件) # 形态 A:封印在场——preview(未开格) 不越封印且零状态写入 diff --git a/game/tests/unit/core/node_map_generator_test.gd b/game/tests/unit/core/node_map_generator_test.gd index c57b11a9..403bdc3a 100644 --- a/game/tests/unit/core/node_map_generator_test.gd +++ b/game/tests/unit/core/node_map_generator_test.gd @@ -16,6 +16,7 @@ var _gen func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _db = ConfigDBScript.new() var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: @@ -77,7 +78,7 @@ func _max_depth(g: Dictionary) -> int: # ---- V1 产出合格:三档×种子扫描,自证零错误 ---- func _test_output_ok() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(产出约束六条+结构铁律组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (产出约束六条+结构铁律组) var all_ok := true var entry_exit_ok := true for L in [1, 2, 3]: @@ -95,7 +96,7 @@ func _test_output_ok() -> void: # ---- V3 可复现:纯函数+counter 无关 ---- func _test_reproducible() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(同种子同参数同图组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (同种子同参数同图组) var same := true for L in [1, 2, 3]: if _sig(_graph(L, 42)) != _sig(_graph(L, 42)): @@ -126,7 +127,7 @@ func _test_reproducible() -> void: # ---- V5 岔路取舍两条 ---- func _test_fork_rules() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:88(岔路取舍组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:88 (岔路取舍组) var next_hop_ok := true var comp_ok := true for L in [1, 2, 3]: @@ -202,7 +203,7 @@ func _branch_types(g: Dictionary, head_id: int, sibling_outs: Array) -> Array: # ---- V6 必经恰一店+归属锁定+多终点深度互异 ---- func _test_shop_and_attribution() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(必经恰一店与归属锁定组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (必经恰一店与归属锁定组) var paths_ok := true var lock_ok := true var depth_ok := true @@ -255,7 +256,7 @@ func _all_paths(g: Dictionary) -> Array: # ---- V7 战斗保底 ---- func _test_battle_floor() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(战斗保底组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (战斗保底组) var ok := true for L in [1, 2, 3]: for s in range(42, 52): @@ -267,7 +268,7 @@ func _test_battle_floor() -> void: # ---- V9 含岔口与出口结构 ---- func _test_exit_structure() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(出口结构与分支互斥组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (出口结构与分支互斥组) var fork_all := true var l1_single := true var big_fork_ok := true @@ -314,7 +315,7 @@ func _terminals_reachable(g: Dictionary, id: int) -> int: # ---- V9 Boss 出场门控(难度曲线与Boss——每区域恰一 Boss 终点)---- func _test_boss_exit_per_region() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48(每区域 Boss 出口组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:48 (每区域 Boss 出口组) # 真实生成多区域图:全部终点 type=="boss"、boss 终点数==exit_count、深度=L−r(r∈[0,exit_count) # ——每区域恰一 boss、深度互异即分属不同区域) # 节点 depth 字段=入口起最长路径深(生成器校验轴);区域序号 r 的可观测形态=最深 boss(r=0) @@ -350,7 +351,7 @@ func _test_boss_exit_per_region() -> void: # ---- V10 RNG 单点(纯流面 counter 不变)---- func _test_rng_single_point() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134(RNG 单点流组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:134 (RNG 单点流组) var svc = RngScript.new() svc.new_adventure(4242) var before: int = svc.counter() @@ -365,7 +366,7 @@ func _test_rng_single_point() -> void: # ---- V1 违规夹具:各报对应错误类别 ---- func _test_negative_fixtures() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:198(无解 fail-fast 夹具组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 (无解 fail-fast 夹具组) # 夹具构造器:手写 nodes/entries/terminals var dup := _fx([ {"id": 0, "type": "battle", "depth": 0, "next": [1]}, @@ -492,7 +493,7 @@ func _has_msg(errs: Array, needle: String) -> bool: # ---- V6 节点图三参数表值驱动(架构整改——fork_third_way_pct/fill_rest_first_pct/width_max 入 level_gen_params)---- func _test_gen_params_table_driven() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129(参数档读表驱动组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:129 (参数档读表驱动组) # ① 同种子真实表行产图逐位(表值驱动回归锚) var g1 := _graph(2, 42) var g2 := _graph(2, 42) diff --git a/game/tests/unit/core/node_map_growth_test.gd b/game/tests/unit/core/node_map_growth_test.gd index 864b8674..570ebce6 100644 --- a/game/tests/unit/core/node_map_growth_test.gd +++ b/game/tests/unit/core/node_map_growth_test.gd @@ -12,6 +12,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) if OS.get_cmdline_user_args().has("fatal-fixture"): _trigger_fatal() return @@ -38,7 +39,7 @@ func _reading(gen, db, L: int) -> Dictionary: # ---- V8 生长递增(三档×多种子逐档读数)---- func _test_growth(gen, db) -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:130(生长递增组) +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:130 (生长递增组) var steps_ok := true var count_ok := true for s in [42, 43, 44, 45, 46]: @@ -62,7 +63,7 @@ func _test_growth(gen, db) -> void: # ---- V4 参数无解判定(静态面:域/预算无解夹具必须被 check_params 拦下)---- func _test_fail_fast_static(gen, db) -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:198(参数无解静态拒绝组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 (参数无解静态拒绝组) Kit.check("生产三档参数档可行性判定零错误〔fv4〕", NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(1)).is_empty() and NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(3)).is_empty()) @@ -81,7 +82,7 @@ func _test_fail_fast_static(gen, db) -> void: # ---- V4 致命中止真实通道(子进程退出码非零;零回退分支=件核)---- func _test_fatal_subprocess() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(无解致命子进程组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (无解致命子进程组) var bin := _engine_bin() if bin == "": Kit.check("未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕", false) @@ -97,6 +98,7 @@ func _test_fatal_subprocess() -> void: func _trigger_fatal() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var gen = GenScript.new(RngScript.new()) # 域内合法但深度预算无解:层深 6 放三出口(check_params 拦下 → 致命中止) var unsolvable := {"layers": 6, "branch_density": 0.0, "battle_ratio": 0.5, "reward_ratio": 0.2, diff --git a/game/tests/unit/core/playtest_logger_guard_test.gd b/game/tests/unit/core/playtest_logger_guard_test.gd index 670be3ab..41c5bdfc 100644 --- a/game/tests/unit/core/playtest_logger_guard_test.gd +++ b/game/tests/unit/core/playtest_logger_guard_test.gd @@ -13,7 +13,7 @@ func _init() -> void: func _smoke_required_injection() -> void: -# oracle: design/engineering/code-conventions.md:40(拆分交接:依赖构造期一次交付——日志器必填注入守卫组) +# oracle: design/engineering/code-conventions.md:40 (拆分交接:依赖构造期一次交付——日志器必填注入守卫组) # 未注入(缺省 0):start_session 返回空串、零文件产出 var lg0 = load("res://presentation/playtest_logger.gd").new() var p0: String = lg0.start_session(42) diff --git a/game/tests/unit/core/pool_case_d_test.gd b/game/tests/unit/core/pool_case_d_test.gd index 814af348..96002e48 100644 --- a/game/tests/unit/core/pool_case_d_test.gd +++ b/game/tests/unit/core/pool_case_d_test.gd @@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/engineering/architecture/architecture.md:107(池结构案 D:候选抽取与池纪律组) +# oracle: design/engineering/architecture/architecture.md:107 (池结构案 D:候选抽取与池纪律组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[pool_case_d_test] FAIL — 数值表加载失败") @@ -66,7 +66,7 @@ func _tiers_of(cands: Array) -> Array: func check_v7(a, db) -> void: -# oracle: design/engineering/architecture/architecture.md:107(池案 D V7 组) +# oracle: design/engineering/architecture/architecture.md:107 (池案 D V7 组) if not a.start_adventure(201): Kit.check_quiet(false, "V7 前置:冒险启动") return @@ -112,7 +112,7 @@ func check_v7(a, db) -> void: func check_v6(b, db) -> void: -# oracle: design/engineering/architecture/architecture.md:107(池案 D V6 组) +# oracle: design/engineering/architecture/architecture.md:107 (池案 D V6 组) if not b.start_adventure(202): Kit.check_quiet(false, "V6 前置:冒险启动") return @@ -142,7 +142,7 @@ func check_v6(b, db) -> void: func check_v12(m, db) -> void: -# oracle: design/engineering/architecture/architecture.md:107(池案 D V12 组) +# oracle: design/engineering/architecture/architecture.md:107 (池案 D V12 组) if not m.start_adventure(401): Kit.check_quiet(false, "V12 前置:冒险启动") return @@ -161,7 +161,7 @@ func check_v12(m, db) -> void: func check_v8(c, db) -> void: -# oracle: design/engineering/architecture/architecture.md:107(池案 D V8 组) +# oracle: design/engineering/architecture/architecture.md:107 (池案 D V8 组) if not c.start_adventure(203): Kit.check_quiet(false, "V8 前置:冒险启动") return diff --git a/game/tests/unit/core/run_time_fatal_test.gd b/game/tests/unit/core/run_time_fatal_test.gd index 0178c303..4df2ce6a 100644 --- a/game/tests/unit/core/run_time_fatal_test.gd +++ b/game/tests/unit/core/run_time_fatal_test.gd @@ -20,7 +20,7 @@ const TAG_OK := "board_gen" # 在册合法标签(PURPOSE_TAGS 含;wr7 必 func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/数据配置.md:199(运行期违约通道——致命真的会死组) +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 (运行期违约通道——致命真的会死组) var db = ConfigDBScript.new() if not db.load_tables("res://data/tables", true): push_error("[run_time_fatal_test] 表加载失败——夹具不可用(回归)") @@ -45,6 +45,7 @@ func _init() -> void: # 缺省形态(契约 2):不接组装点的协作件违约只落自身 push_error+assert,不外溢封印 func _default_form(db) -> bool: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var rng = RngScript.new() rng.set_master_seed(3) var oc = OrchestratorScript.new(db, rng) @@ -69,6 +70,7 @@ func _default_form(db) -> bool: # (棋盘生成半边:装板域拆件后生成器随 BoardInstaller 件内自建——组装点=引用束 fatal_cb 接线, # 经新组装点构建后触发生成器违约,封印传导断言不变) func _seal_propagation(db) -> bool: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var rng_a = RngScript.new() rng_a.set_master_seed(5) var oc_bg = OrchestratorScript.new(db, rng_a) @@ -90,6 +92,7 @@ func _seal_propagation(db) -> bool: # ②wr6(p6):未知用途标签 → handler 记录位为真(判据主体)+拿不到可用 RNG;合法标签对照另行验过 func _unknown_tag_channel() -> bool: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var rng_bad = RngScript.new() rng_bad.set_master_seed(7) if not _inject_handler(rng_bad, "wr6"): @@ -108,6 +111,7 @@ func _unknown_tag_channel() -> bool: # ③wr7(p7):未播种取数 → handler 记录位为真+拿不到可用 RNG;同标签播种对照另行验过 func _unseeded_channel() -> bool: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var rng_bad = RngScript.new() # 不播种(_master_seed 缺省 -1) if not _inject_handler(rng_bad, "wr7"): return false @@ -125,6 +129,7 @@ func _unseeded_channel() -> bool: # 成员在位前提检查(成员被移除时走干净退出 2,不落「赋值不存在的属性」式中途中断) func _inject_handler(rng, label: String) -> bool: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) if not (rng.get("violation_handler") is Callable): push_error("[run_time_fatal_test/%s] violation_handler 成员缺失(p6/p7 形态未实装——回归)" % label) return false diff --git a/game/tests/unit/core/save_boot_no_activate_test.gd b/game/tests/unit/core/save_boot_no_activate_test.gd index 262b88b7..87aced80 100644 --- a/game/tests/unit/core/save_boot_no_activate_test.gd +++ b/game/tests/unit/core/save_boot_no_activate_test.gd @@ -22,7 +22,7 @@ func _init() -> void: # ① 无参实例(autoload 实例化形态)=无激活态+默认合法空态(sv2 类级) func _test_no_arg_instance_has_no_activation() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(启动态无激活不读写组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (启动态无激活不读写组) var sm := SaveManagerScript.new() Kit.check_quiet(sm.slots.active_slot() == 0 and not sm.slots.has_write_target(), "sv2 无参实例无激活态(路径空/激活号 0/写盘目标空)〔卡① sv2〕") @@ -41,7 +41,7 @@ func _test_no_arg_instance_has_no_activation() -> void: # 判别锚:空 _path 的退化写目标=裸 ".tmp"(落 res://,拆闸实验实测杂散落点)—— # 断言先清退化目标再核零新增,闸短路缺陷(拒绝口旁路、写盘真实发生)必红。 func _test_write_rejected_without_target() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(无激活态写盘拒绝组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (无激活态写盘拒绝组) var sm := SaveManagerScript.new() var before := {} for i in range(1, 4): @@ -81,7 +81,7 @@ func _test_write_rejected_without_target() -> void: # (生产结算链全段在位:mark_first_adventure_done→apply_adventure_summary→refresh_treasure_unlocks # →write_to_disk;无激活态下落账内存、写盘拒绝、盘面零触碰——已知中间态的机检面) func _test_settle_chain_write_rejected() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(结算链写盘拒绝组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (结算链写盘拒绝组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): Kit.check_quiet(false, "sv3 链测试前置:数值表加载失败〔卡① sv3〕") @@ -104,7 +104,7 @@ func _test_settle_chain_write_rejected() -> void: # ③ 越界槽号三原语拒绝+窥视越界返回不可读(原语边界 fail-safe) func _test_out_of_range_slots_rejected() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三存档位域界——越域槽拒绝组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (三存档位域界——越域槽拒绝组) var sm := SaveManagerScript.new() sm.slots.attach_slot_template("user://smoke_boot_noact_%d.json") Kit.check_quiet(not sm.slots.activate_slot(0) and not sm.slots.activate_slot(4) diff --git a/game/tests/unit/core/save_manager_domain_test.gd b/game/tests/unit/core/save_manager_domain_test.gd index bb47f190..643d4c3a 100644 --- a/game/tests/unit/core/save_manager_domain_test.gd +++ b/game/tests/unit/core/save_manager_domain_test.gd @@ -19,6 +19,7 @@ var _db func _init() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2–G5 同款:表损坏的红落在具体断言上) var load_ok: bool = _db.load_tables("res://data/tables", true) @@ -46,7 +47,7 @@ func _fresh_rng(seed_val: int): # 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离, # 段首清场+段尾清理——绝不碰 user://save.json(红线)。 func _smoke_save() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176(存档域读写往返/迁移链/损坏兜底/域界组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176 (存档域读写往返/迁移链/损坏兜底/域界组) print("--- SaveManager 存档读写/迁移/兜底 ---") var sm_script = load("res://core/save_manager.gd") var paths := ["user://smoke_save_roundtrip.json", "user://smoke_save_fresh.json", @@ -339,7 +340,7 @@ func _smoke_save() -> void: # 测试文件统一 user://smoke_save_slot_%d.json 模板隔离(attach_slot_template 注入口——禁各自拼槽名), # 段首清场+段尾清理——绝不碰 user://save.json 与真实槽 user://save_slot_N.json(红线)。 func _smoke_save_slots() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三槽位与摘要 cap——槽位域组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (三槽位与摘要 cap——槽位域组) print("--- SaveManager 三存档位承载(迭代 33 卡①)---") var sm_script = load("res://core/save_manager.gd") var slot_tpl := "user://smoke_save_slot_%d.json" diff --git a/game/tests/unit/core/save_slot_isolation_test.gd b/game/tests/unit/core/save_slot_isolation_test.gd index 6afdd4fe..743d77c1 100644 --- a/game/tests/unit/core/save_slot_isolation_test.gd +++ b/game/tests/unit/core/save_slot_isolation_test.gd @@ -38,7 +38,7 @@ func _write_v7_keystone(path: String, keystone: int) -> void: # ① 三槽往返:各槽写异值 → 写盘 → 新实例按位读回逐域相等;改槽 1 槽 2 字节不动(sv1) func _test_three_slot_roundtrip_and_isolation() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(三槽位独立文件隔离组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (三槽位独立文件隔离组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) for i in range(1, 4): @@ -66,7 +66,7 @@ func _test_three_slot_roundtrip_and_isolation() -> void: # ② 高版本槽进档失败:拒绝+恢复切换前三态(路径/激活号/数据)+拒读槽零误写(sv9/sv10) func _test_activate_failure_restores_previous_slot() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61(激活失败回滚组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:61 (激活失败回滚组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_v7_keystone(TPL % 1, 311) @@ -88,7 +88,7 @@ func _test_activate_failure_restores_previous_slot() -> void: # ③ MetaProgression 宿主链切槽:引用同一性保持+写盘落当前槽(sv10——禁换实例先例) func _test_metaprogression_reference_identity() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176(元进度引用同一性——往返一致组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:176 (元进度引用同一性——往返一致组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_v7_keystone(TPL % 1, 533) diff --git a/game/tests/unit/core/save_slot_summary_test.gd b/game/tests/unit/core/save_slot_summary_test.gd index b4354562..47094660 100644 --- a/game/tests/unit/core/save_slot_summary_test.gd +++ b/game/tests/unit/core/save_slot_summary_test.gd @@ -36,7 +36,7 @@ func _write_slot_json(path: String, content: String) -> void: # ① 三槽异值逐槽三项正确+不激活不写内存(sv7) func _test_peek_three_slots_distinct_values() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(槽位摘要按槽窥视组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174 (槽位摘要按槽窥视组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) for i in range(1, 4): @@ -58,7 +58,7 @@ func _test_peek_three_slots_distinct_values() -> void: # ② 空槽零值合法态+不可读三态显式标记(sv7/契约 9) func _test_peek_empty_and_unreadable_states() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(坏槽三态组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174 (坏槽三态组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) DirAccess.remove_absolute(TPL % 3) # 清①段残留——空槽夹具 @@ -79,7 +79,7 @@ func _test_peek_empty_and_unreadable_states() -> void: # ③ 窥视零副作用:字节+mtime 双比对不变+零 .corrupt.bak 改名(sv8——真实入口冒烟段同判据) func _test_peek_zero_side_effect() -> void: -# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174(窥视零副作用组) +# oracle: design/gdd/systems/表现与支撑/存档序列化.md:174 (窥视零副作用组) var t := SaveManagerScript.new() t.slots.attach_slot_template(TPL) _write_slot_json(TPL % 1, "corrupt bytes {{{{") diff --git a/game/tests/unit/core/settle_segments_test.gd b/game/tests/unit/core/settle_segments_test.gd index edebe932..d209504c 100644 --- a/game/tests/unit/core/settle_segments_test.gd +++ b/game/tests/unit/core/settle_segments_test.gd @@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/gdd/systems/战斗循环/战斗结算.md:362(结算五段 Segment——五段全序组) +# oracle: design/gdd/systems/战斗循环/战斗结算.md:362 (结算五段 Segment——五段全序组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): print("[settle_segments_test] FAIL — 数值表加载失败") diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 16e184c7..3a989daa 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -10,7 +10,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: -# oracle: design/gdd/systems/跨局循环/秘宝.md:246(秘宝 12 件逐件行为——效果行为组) +# oracle: design/gdd/systems/跨局循环/秘宝.md:246 (秘宝 12 件逐件行为——效果行为组) var db = load("res://core/config_db.gd").new() if not db.load_tables("res://data/tables", false): Kit.check_quiet(false, "数值表加载") diff --git a/game/tests/unit/core/treasure_items_test.gd b/game/tests/unit/core/treasure_items_test.gd index c46ee17f..f998c777 100644 --- a/game/tests/unit/core/treasure_items_test.gd +++ b/game/tests/unit/core/treasure_items_test.gd @@ -53,7 +53,7 @@ func _all_revealed(_c: Vector2i) -> bool: func _init() -> void: -# oracle: design/gdd/systems/跨局循环/秘宝.md:246(秘宝结构冻结与护栏校验——结构组) +# oracle: design/gdd/systems/跨局循环/秘宝.md:246 (秘宝结构冻结与护栏校验——结构组) # ① 授予归因+受理序+Used 不可逆 var inv = _new_inv() var r1: Dictionary = inv.add_item("sapper_order", "safe_cell", Vector2i(3, 4), 2) diff --git a/game/tests/unit/core/tree_requires_level_test.gd b/game/tests/unit/core/tree_requires_level_test.gd index 1434ce5a..e3b1a907 100644 --- a/game/tests/unit/core/tree_requires_level_test.gd +++ b/game/tests/unit/core/tree_requires_level_test.gd @@ -7,7 +7,7 @@ extends SceneTree func _init() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:195(等级阈值前置:schema 四拒一过+判定三态+投影两态组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:195 (等级阈值前置:schema 四拒一过+判定三态+投影两态组) var failures := 0 var db = load("res://core/config_db.gd").new() var mt = load("res://data/schemas/meta_tree_table.gd").new() diff --git a/game/tests/unit/core/tree_rules_v11_test.gd b/game/tests/unit/core/tree_rules_v11_test.gd index 834881ef..f737a0d9 100644 --- a/game/tests/unit/core/tree_rules_v11_test.gd +++ b/game/tests/unit/core/tree_rules_v11_test.gd @@ -12,7 +12,7 @@ extends SceneTree func _init() -> void: -# oracle: design/gdd/systems/跨局循环/天赋树.md:194(结构封闭:六键合成行闭集校验组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (结构封闭:六键合成行闭集校验组) var eng = load("res://core/effect_engine.gd") var tree_tbl = load("res://data/schemas/meta_tree_table.gd") var db = load("res://core/config_db.gd").new() @@ -89,7 +89,7 @@ func _init() -> void: # ---- 规则键参数域校验(架构整改批 R1——六键参数字典):正例全过/缺参数拒/多参数拒/越域拒/非字典拒 ---- func _test_rule_key_params(eng, tree_tbl, db) -> Array: -# oracle: design/gdd/systems/跨局循环/天赋树.md:194(规则键参数字典——参数名/取值域合规组) +# oracle: design/gdd/systems/跨局循环/天赋树.md:194 (规则键参数字典——参数名/取值域合规组) var errs: Array = [] var params_tbl: Dictionary = tree_tbl.RULE_KEY_PARAMS # 正例:meta_tree.tres 现行 Fork 行逐键过 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 e52a6cbf..91c09323 100644 --- a/game/tests/unit/data_config/table_structure_guard_test.gd +++ b/game/tests/unit/data_config/table_structure_guard_test.gd @@ -576,6 +576,7 @@ func _conversion_row_of(rows: Array, attr: String) -> Dictionary: # ---- V7 节点图三新键域校验(架构整改——缺新键行拒/width_max 越域拒/两 pct 越域拒/表值全过)---- func _test_level_gen_new_fields() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var tbl = _db.run.level_gen_params var mk := func(mod: Dictionary) -> Dictionary: var row := {"level_index": 1, "layers": 6, "branch_density": 0.3, "battle_ratio": 0.5, "reward_ratio": 0.2, @@ -612,6 +613,7 @@ func _test_level_gen_new_fields() -> void: # ---- 变异表头域校验 / feat cap 必填(架构整改——丁案三件的数据面守卫)---- func _test_variation_header_domains() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var inst = load("res://data/schemas/variation_table.gd").new() inst.percent_of_cells = 5 inst.per_level_bonus = 0 @@ -629,6 +631,7 @@ func _test_variation_header_domains() -> void: func _test_feat_cap_required() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var inst = load("res://data/schemas/feat_table.gd").new() # 生产表全行 cap 在场(加载期已验——此处显式复核 37 行) var all_cap := true diff --git a/game/tests/unit/example_test.gd b/game/tests/unit/example_test.gd index e5412e1a..0dd7c944 100644 --- a/game/tests/unit/example_test.gd +++ b/game/tests/unit/example_test.gd @@ -10,7 +10,7 @@ extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:check/汇总退出样板单点 func _init() -> void: -# oracle: design/engineering/test-conventions.md:37(套件协议模板——执行载体与退出码范例组) +# oracle: design/engineering/test-conventions.md:37 (套件协议模板——执行载体与退出码范例组) Kit.check("headless SceneTree 协议可用(quit(code) 通道)", _test_headless_protocol()) Kit.check("命名规范可执行:test_[scenario]_[expected]", _test_naming_convention_followed()) quit(Kit.finish("example_test")) diff --git a/game/tests/unit/presentation/battle_narrator_test.gd b/game/tests/unit/presentation/battle_narrator_test.gd index a5153d9c..f037d1ac 100644 --- a/game/tests/unit/presentation/battle_narrator_test.gd +++ b/game/tests/unit/presentation/battle_narrator_test.gd @@ -18,7 +18,7 @@ class _BossRunner: func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(战斗叙述宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (战斗叙述宿主——装载组) _test_numeric_lines_carry_values() _test_end_text_differs_by_result() _test_phase_text_nonempty() @@ -29,7 +29,7 @@ func _init() -> void: func _test_numeric_lines_carry_values() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(结算行数值带入组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (结算行数值带入组) var rest: String = NarratorScript.rest_line(12, 30, 40) Kit.check("休整叙述带入回复量与当前血量(实际「%s」)" % rest, rest.contains("12") and rest.contains("30") and rest.contains("40")) @@ -40,7 +40,7 @@ func _test_numeric_lines_carry_values() -> void: func _test_end_text_differs_by_result() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(胜负互异文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (胜负互异文案组) # 关卡收束(level_end=true)恒为「关卡完成!」——单场胜负文案只在非收束态出现 var level: String = NarratorScript.battle_end_text("lose", true) Kit.check("关卡收束态恒为「关卡完成!」(实际「%s」)" % level, level == "关卡完成!") @@ -53,7 +53,7 @@ func _test_end_text_differs_by_result() -> void: func _test_phase_text_nonempty() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(阶段行非空组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (阶段行非空组) var t: String = NarratorScript.phase_text(1, "", false, false, "") Kit.check("相位叙述非空(实际「%s」)" % t, t != "") var boss: String = NarratorScript.phase_text(2, "", false, true, "灰矮人石君王") @@ -63,7 +63,7 @@ func _test_phase_text_nonempty() -> void: # 叙述寻址按行类取 Boss 行集(S 清单 S15 换代):Boss 节点名必须来自 (关卡, boss, 带) 行集, # 不能落到同深度的战斗行集——期望名由表行集首行读出,不写死字面量。 func _test_boss_name_uses_boss_rowset() -> void: -# oracle: design/gdd/systems/战斗循环/敌人系统.md:295(Boss 名读行集组) +# oracle: design/gdd/systems/战斗循环/敌人系统.md:295 (Boss 名读行集组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "叙述寻址用例:数值表加载成功") @@ -92,7 +92,7 @@ func _test_boss_name_uses_boss_rowset() -> void: # 关卡完成停点文案族措辞钉子(口径换代批):回血行/满血合法态/渠道句/摘要关数行—— # 旧措辞退役后玩家可见面由本组断言钉在新措辞上(撤销证明:回退 battle_narrator 对应行→断言红→复原复绿) func _test_level_clear_wording() -> void: -# oracle: design/gdd/ux/level-clear.md:11(关卡结算屏文案组) +# oracle: design/gdd/ux/level-clear.md:11 (关卡结算屏文案组) var healed: String = NarratorScript.curio_pick_header({"level_index": 1, "heal_gained": 5, "hp_before": 10, "hp_after": 15}) Kit.check("关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" % healed, healed.contains("关卡完成回血 +5(生命 10 → 15)")) @@ -112,6 +112,7 @@ func _test_level_clear_wording() -> void: # 真实入口=呈现状态快照函数 presentation_snapshot:A 有削弱 B 无 → 两卡投影各现各的(零共享引用) func _test_snapshot_effects_per_unit() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var db = load("res://core/config_db.gd").new() Kit.check("V8 前置:表加载零 error", db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0) var rng = load("res://core/rng_service.gd").new() diff --git a/game/tests/unit/presentation/camp_scene_layout_test.gd b/game/tests/unit/presentation/camp_scene_layout_test.gd index 9da306de..a4a74b07 100644 --- a/game/tests/unit/presentation/camp_scene_layout_test.gd +++ b/game/tests/unit/presentation/camp_scene_layout_test.gd @@ -10,12 +10,12 @@ const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest" func _initialize() -> void: -# oracle: design/gdd/ux/camp.md:29(营地场景布局数据件——纵深与地平线组) +# oracle: design/gdd/ux/camp.md:29 (营地场景布局数据件——纵深与地平线组) call_deferred("_test_layout_hotspot_geometry") func _test_layout_hotspot_geometry() -> void: -# oracle: design/gdd/ux/camp.md:16(七物件命中区几何红线组) +# oracle: design/gdd/ux/camp.md:16 (七物件命中区几何红线组) var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres") Kit.check("V14 布局表加载且 schema 类在位〔营地图批 V14〕", table is CampSceneLayoutTable and table != null) if table == null: diff --git a/game/tests/unit/presentation/forge_workbench_test.gd b/game/tests/unit/presentation/forge_workbench_test.gd index a4f94893..907ec41b 100644 --- a/game/tests/unit/presentation/forge_workbench_test.gd +++ b/game/tests/unit/presentation/forge_workbench_test.gd @@ -10,7 +10,7 @@ const _TEST_SAVE := "user://test_forge_workbench.json" func _initialize() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219(工作间两区六路线——装载组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219 (工作间两区六路线——装载组) call_deferred("_run") @@ -22,7 +22,7 @@ func _row_by_id(ov) -> Dictionary: func _run() -> void: -# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219(六路线 UI 直验组) +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219 (六路线 UI 直验组) var db = load("res://core/config_db.gd").new() var load_ok: bool = db.load_tables("res://data/tables", true) if not load_ok: diff --git a/game/tests/unit/presentation/hud_presenter_test.gd b/game/tests/unit/presentation/hud_presenter_test.gd index 5aaba4c7..2c85c0d1 100644 --- a/game/tests/unit/presentation/hud_presenter_test.gd +++ b/game/tests/unit/presentation/hud_presenter_test.gd @@ -9,7 +9,7 @@ const HudScript := preload("res://presentation/hud_presenter.gd") func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(HUD 投影宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (HUD 投影宿主——装载组) _test_accepted_text() _test_reject_text_by_reason() _test_unknown_reason_fallback() @@ -17,13 +17,13 @@ func _init() -> void: func _test_accepted_text() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(受理反馈呈现组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212 (受理反馈呈现组) var t: String = HudScript.outcome_text({"accepted": true}) Kit.check("受理态文案固定为「执行完成。」(实际「%s」)" % t, t == "执行完成。") func _test_reject_text_by_reason() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(拒绝三源可辨文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212 (拒绝三源可辨文案组) var cases := { "not_adjacent": "不能执行:目标与小人不相邻", "target_revealed": "不能执行:目标已开", @@ -42,7 +42,7 @@ func _test_reject_text_by_reason() -> void: func _test_unknown_reason_fallback() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212(未知兜底文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:212 (未知兜底文案组) var t: String = HudScript.outcome_text({"accepted": false, "reject_reason": "brand_new_reason"}) Kit.check("未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" % t, t == "动作被拒。") var sub: String = HudScript.outcome_text({ diff --git a/game/tests/unit/presentation/level_map_info_fog_test.gd b/game/tests/unit/presentation/level_map_info_fog_test.gd index b0f42d62..d103f20b 100644 --- a/game/tests/unit/presentation/level_map_info_fog_test.gd +++ b/game/tests/unit/presentation/level_map_info_fog_test.gd @@ -11,12 +11,12 @@ const ScreenScript := preload("res://presentation/views/level_map_screen.gd") func _initialize() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(信息迷雾呈现宿主——装载组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (信息迷雾呈现宿主——装载组) call_deferred("_run") func _run() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(迷雾显隐宿主组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (迷雾显隐宿主组) var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "迷雾呈现用例:数值表加载成功") @@ -48,7 +48,7 @@ func _runner(db, nodes: Array, candidates: Array, n: int = 2): # ---- ① 范围外只见结构:雾下节点类型图标与文字均不绘制〔fv2〕---- func _test_fog_hides_type() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(雾下类型隐藏组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (雾下类型隐藏组) var node := {"id": 3, "type": "battle", "depth": 3, "next": []} var shown: Dictionary = ScreenScript.node_display(node, 2, [2], false, false) Kit.check("可见集内节点:图标/类型文字均绘制〔fv2〕", @@ -62,7 +62,7 @@ func _test_fog_hides_type() -> void: # ---- ② Boss/终点不入雾+多终点标记〔fv3〕---- func _test_boss_exempt_and_multi_boss() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(Boss 描边豁免与多 Boss 组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (Boss 描边豁免与多 Boss 组) var type_vis: Array = [1, 7, 9] # 双 Boss 图:7/9 均在可见集 var boss_a: Dictionary = ScreenScript.node_display({"id": 7, "type": "boss", "depth": 7, "next": []}, 7, type_vis, false, false) var boss_b: Dictionary = ScreenScript.node_display({"id": 9, "type": "boss", "depth": 9, "next": []}, 9, type_vis, false, false) @@ -80,7 +80,7 @@ func _test_boss_exempt_and_multi_boss() -> void: # ---- ③ 节点内详情图上从不显示〔fv5〕---- func _test_details_never_shown() -> void: -# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178(详情零显示组) +# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:178 (详情零显示组) var plain := {"id": 3, "type": "battle", "depth": 3, "next": [4]} var loaded := {"id": 3, "type": "battle", "depth": 3, "next": [4], "enemy_id": "goblin_secret", "reward_id": "reward_secret"} var a: Dictionary = ScreenScript.node_display(plain, 3, [3], false, true) @@ -97,6 +97,7 @@ func _test_details_never_shown() -> void: # ---- ④ label_text 三态文案〔fv2〕---- func _test_label_three_states() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var node := {"id": 3, "type": "battle", "depth": 3, "next": [4]} Kit.check("当前位文案=「当前位置」〔fv2〕", String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置") @@ -149,6 +150,7 @@ func _big_graph_nodes() -> Array: func _test_big_graph_fit(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var nodes := _big_graph_nodes() var entry: Dictionary = nodes[0] var runner = _runner(db, nodes, [nodes[1]]) @@ -202,6 +204,7 @@ func _test_big_graph_fit(db) -> void: # 命中读数 = is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器。 func _test_layout_cache(db) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var nodes := _big_graph_nodes() var runner = _runner(db, nodes, [nodes[1]]) var screen = ScreenScript.new() diff --git a/game/tests/unit/presentation/modal_flow_test.gd b/game/tests/unit/presentation/modal_flow_test.gd index 892eaa4e..6e2db94e 100644 --- a/game/tests/unit/presentation/modal_flow_test.gd +++ b/game/tests/unit/presentation/modal_flow_test.gd @@ -11,7 +11,7 @@ const ModalStackScript := preload("res://presentation/modal_stack.gd") func _init() -> void: -# oracle: design/engineering/code-conventions.md:54(模态编排纯逻辑——层级与输入锁组) +# oracle: design/engineering/code-conventions.md:54 (模态编排纯逻辑——层级与输入锁组) # 哑层与桩 Callable(纯逻辑类零场景前提的证明面) var start := Control.new() var inter := Control.new() diff --git a/game/tests/unit/presentation/pacing_queue_test.gd b/game/tests/unit/presentation/pacing_queue_test.gd index 5992ea18..0e16413e 100644 --- a/game/tests/unit/presentation/pacing_queue_test.gd +++ b/game/tests/unit/presentation/pacing_queue_test.gd @@ -37,12 +37,13 @@ class FakeFx extends SettlementFxView: func check(cond: bool, msg: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) if not cond: _problems.append(msg) func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(排播节奏——排空零分叉/零丢行/延迟双态组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (排播节奏——排空零分叉/零丢行/延迟双态组) var main: Node = load("res://presentation/main.tscn").instantiate() root.add_child(main) await process_frame @@ -156,6 +157,7 @@ func _count_row(row: Dictionary, arr: Array) -> void: func _finish() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) for p in ["user://test_pacing_queue_save.json", "user://test_pacing_queue_save.json.tmp"]: if FileAccess.file_exists(p): DirAccess.remove_absolute(p) diff --git a/game/tests/unit/presentation/player_copy_sweep_test.gd b/game/tests/unit/presentation/player_copy_sweep_test.gd index 0da52ecc..de41e562 100644 --- a/game/tests/unit/presentation/player_copy_sweep_test.gd +++ b/game/tests/unit/presentation/player_copy_sweep_test.gd @@ -23,7 +23,7 @@ var _db func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(玩家面文案清扫宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281 (玩家面文案清扫宿主——装载组) _db = ConfigDBScript.new() var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: @@ -43,7 +43,7 @@ func _init() -> void: # ① C 区两段头(u1/u2——直建 hero 面板,adventure_feats_test ⑦c 族同构) func _sweep_hero_panel() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(英雄面板文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 (英雄面板文案组) var panel = load("res://presentation/views/hero_info_panel_view.gd").new(_db.presentation.battle_layout) var feat_head := _first_label(panel._feat_section) var curio_head := _first_label(panel._curio_section) @@ -55,7 +55,7 @@ func _sweep_hero_panel() -> void: # ②③ 奖励节点头部+专长卡兜底(u3/u11——静态直调) func _sweep_narrator() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(叙述文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (叙述文案组) var narrator = load("res://presentation/battle_narrator.gd") var head := String(narrator.node_curio_header()) Kit.check("奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕", head.contains("冒险牌库")) @@ -68,7 +68,7 @@ func _sweep_narrator() -> void: # ④ 图例封印行(u4——直建 LegendOverlay 读 ENTRIES 常量) func _sweep_legend() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(图例文案组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267 (图例文案组) load("res://presentation/views/legend_overlay.gd").new() # 直建(_init(rap_cost) 现签名) var seal_row: Array = [] for e in load("res://presentation/views/legend_overlay.gd").ENTRIES: @@ -84,7 +84,7 @@ func _sweep_legend() -> void: # ⑤⑥ 来源名双源(u5/u6——状态槽消费半+表值 ConfigDB 真装载+battle_log 行消费半) func _sweep_source_names() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(来源名读表组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 (来源名读表组) var row_view = load("res://presentation/views/status_slot_row.gd").new() row_view.set_effects([{"effect_id": "alert", "source_class": "mine_hit", "display_name": "危险格受击", "remaining_settlements": 2, "round_index": 1}]) @@ -110,7 +110,7 @@ func _sweep_source_names() -> void: # ⑦⑧ 已开危险格字形族(u7/u8/u9——BoardView rebuild 真实刷新路径+源 token 扫描) func _sweep_board_glyph() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264(危险格字形组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (危险格字形组) var board_generator = load("res://core/board_generator.gd").new() var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db) var ae = load("res://core/action_economy.gd").new(_db) @@ -149,6 +149,7 @@ func _sweep_board_glyph() -> void: # ⑧⑩ 源 token 扫描(注释剥离=行首 # 判——契约 2 保留面不入域) func _sweep_token_scans() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var board_src := FileAccess.get_file_as_string("res://presentation/views/board_view.gd") Kit.check("board_view.gd 源零 \"雷\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕", board_src.count("\"雷\"") == 0) @@ -166,7 +167,7 @@ func _sweep_token_scans() -> void: # 刷新两 toast 文案零数字字面量(架构整改 R4——格式串零数字、插值位与表字段一一对应; # 静态判据+评审注记观测面局限:真机文案随表值由实机目检兜——T10 清单两项) func _sweep_refresh_toast_no_literals() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(刷新提示读表拼接——零字面量组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (刷新提示读表拼接——零字面量组) var main_src := FileAccess.get_file_as_string("res://presentation/main.gd") var rap_toast := "" var cap_toast := "" diff --git a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd index 461752d3..caee31ea 100644 --- a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd +++ b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd @@ -33,7 +33,7 @@ const A1_FILES := { func _init() -> void: -# oracle: design/engineering/code-conventions.md:41(presentation 封装——四族零命中扫描组) +# oracle: design/engineering/code-conventions.md:41 (presentation 封装——四族零命中扫描组) _sweep_a1_data_classes() _sweep_a2_subparts() _sweep_b_combo_root() @@ -43,7 +43,7 @@ func _init() -> void: # 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) func _src(path: String) -> String: -# oracle: design/engineering/code-conventions.md:41(源码采样组) +# oracle: design/engineering/code-conventions.md:41 (源码采样组) var src := FileAccess.get_file_as_string(path) Kit.check("扫描面非空前置:%s" % path, src.length() > 0) return src @@ -51,21 +51,21 @@ func _src(path: String) -> String: # 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位;suffix 区分族组) func _zero(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: -# oracle: design/engineering/code-conventions.md:41(零命中扫描组) +# oracle: design/engineering/code-conventions.md:41 (零命中扫描组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 零命中〔%s〕" % [label, pattern, suffix], re.search(src) == null) # 正锚在案断言(正则命中 ≥1——新公开面/新形态在用;suffix 区分族组) func _anchor(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: -# oracle: design/engineering/code-conventions.md:41(正锚存在组) +# oracle: design/engineering/code-conventions.md:41 (正锚存在组) var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 正锚在案〔%s〕" % [label, pattern, suffix], re.search(src) != null) # ② V2 A2 拆件子件(行为归位子件公开方法、读走公开读法、身份构造交付) func _sweep_a2_subparts() -> void: -# oracle: design/engineering/code-conventions.md:41(A2 子件封装扫描组) +# oracle: design/engineering/code-conventions.md:41 (A2 子件封装扫描组) var body := _src("res://presentation/views/standee_body.gd") if body.length() > 0: _zero("\\bst\\._|\\bmo\\._", body, "子件私取模式零命中(standee_body)", "pres封装 V2") @@ -82,7 +82,7 @@ func _sweep_a2_subparts() -> void: # ③ V3 B 组合根交接面(main+五分文件——成对单行转发梯换代为公开入口+Callable 束) func _sweep_b_combo_root() -> void: -# oracle: design/engineering/code-conventions.md:41(组合根束封装扫描组) +# oracle: design/engineering/code-conventions.md:41 (组合根束封装扫描组) var b_files := { "res://presentation/main.gd": false, "res://presentation/main_ui_build.gd": true, @@ -117,6 +117,7 @@ func _sweep_b_combo_root() -> void: # ④ V4 C 杂项(UiKit 私有工厂公开化+cell_mark 构造交付) func _sweep_c_misc() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var offenders: PackedStringArray = [] var scanned := 0 _walk_gd_for("res://", "UiKit\\._[a-zA-Z]", "res://presentation/views/ui_kit.gd", offenders) @@ -154,6 +155,7 @@ func _walk_gd_for(dir_path: String, pattern: String, exclude: String, offenders: # ① V1 A1 三内部数据类(字段公开化——宿主直读写内类私有字段清零) func _sweep_a1_data_classes() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) for path in A1_FILES: var spec: Dictionary = A1_FILES[path] var src := _src(path) diff --git a/game/tests/unit/presentation/reward_overlay_test.gd b/game/tests/unit/presentation/reward_overlay_test.gd index f6323e36..51937212 100644 --- a/game/tests/unit/presentation/reward_overlay_test.gd +++ b/game/tests/unit/presentation/reward_overlay_test.gd @@ -9,7 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持) func _req(desc: String, ok: bool) -> bool: -# oracle: design/gdd/ux/node-reward.md:9(奖励覆盖层直验——需求包装组) +# oracle: design/gdd/ux/node-reward.md:9 (奖励覆盖层直验——需求包装组) Kit.check(desc, ok) if not ok: quit(1) @@ -17,11 +17,11 @@ func _req(desc: String, ok: bool) -> bool: return true func _initialize() -> void: -# oracle: design/gdd/ux/node-reward.md:9(奖励节点装载组) +# oracle: design/gdd/ux/node-reward.md:9 (奖励节点装载组) call_deferred("_run") func _run() -> void: -# oracle: design/gdd/ux/node-reward.md:9(奖励三选一呈现组) +# oracle: design/gdd/ux/node-reward.md:9 (奖励三选一呈现组) var ov = load("res://presentation/views/curio_pick_overlay.gd").new() root.add_child(ov) var picked := [] diff --git a/game/tests/unit/presentation/seal_mark_params_test.gd b/game/tests/unit/presentation/seal_mark_params_test.gd index f13d220b..6e7e4bb5 100644 --- a/game/tests/unit/presentation/seal_mark_params_test.gd +++ b/game/tests/unit/presentation/seal_mark_params_test.gd @@ -10,7 +10,7 @@ const CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd") func _init() -> void: -# oracle: design/gdd/ux/battle.md:128(封印印记参数宿主——装载组) +# oracle: design/gdd/ux/battle.md:128 (封印印记参数宿主——装载组) _test_param_values() _test_shader_uniforms() _test_fog_gate() @@ -19,7 +19,7 @@ func _init() -> void: # R1–R3/R7 前置:五常量取值逐项相等(art-bible §8.2 定值) func _test_param_values() -> void: -# oracle: design/gdd/ux/battle.md:128(环径与法阵定值组) +# oracle: design/gdd/ux/battle.md:128 (环径与法阵定值组) Kit.check("SEAL_RING_FRAC = 0.82〔seal批 V1〕", UiKit.SEAL_RING_FRAC == 0.82) Kit.check("SEAL_WALL_FRAC = 0.11〔seal批 V2〕", UiKit.SEAL_WALL_FRAC == 0.11) Kit.check("SEAL_ARM_FRAC = 0.156〔seal批 V2〕", UiKit.SEAL_ARM_FRAC == 0.156) @@ -29,7 +29,7 @@ func _test_param_values() -> void: # R1–R3/R7:着色器存在+七个 uniform 名在文(参数注入面契约) func _test_shader_uniforms() -> void: -# oracle: design/gdd/ux/battle.md:128(着色器参数化绘制组) +# oracle: design/gdd/ux/battle.md:128 (着色器参数化绘制组) var src := FileAccess.get_file_as_string(SEAL_SHADER) Kit.check("着色器文件存在且非空:%s〔seal批 V1〕" % SEAL_SHADER, src.length() > 0) if src.length() == 0: @@ -43,7 +43,7 @@ func _test_shader_uniforms() -> void: # R4 迷雾门控:seal_on × fog_state → 封印子件可见性(内部字段直查=受控例外——子件可见性无公开可观测面, # 卡面 R4 断言即 prescribe 子件 visible 直查;对照表该行注记理由) func _test_fog_gate() -> void: -# oracle: design/gdd/ux/battle.md:128(迷雾联动门控组) +# oracle: design/gdd/ux/battle.md:128 (迷雾联动门控组) var u: Node2D = CELL_UNIT.new() u.seal_on = true Kit.check("seal_on 即挂封印子件〔seal批 V4〕", u._seal_layer != null) diff --git a/game/tests/unit/presentation/settings_store_test.gd b/game/tests/unit/presentation/settings_store_test.gd index 4a4710f5..67aa6df4 100644 --- a/game/tests/unit/presentation/settings_store_test.gd +++ b/game/tests/unit/presentation/settings_store_test.gd @@ -12,7 +12,7 @@ const ISO_PATH := "user://settings_store_unit_test.cfg" # 隔离注路径( func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置存储宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (设置存储宿主——装载组) _test_settings_roundtrip() _test_settings_default() _test_settings_apply_chain() @@ -27,7 +27,7 @@ func _cleanup() -> void: # ---- 持久化回读(写 true→存→新实例回读 true;节/键 motion/reduced)---- func _test_settings_roundtrip() -> void: -# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(设置写读往返组) +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (设置写读往返组) var w = StoreScript.new(ISO_PATH) w.reduced_motion = true Kit.check("前置:save_settings 落盘成功", w.save_settings()) @@ -44,7 +44,7 @@ func _test_settings_roundtrip() -> void: # ---- 缺省值(无文件缺省 false)---- func _test_settings_default() -> void: -# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(出厂默认组) +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (出厂默认组) var absent_path := "user://settings_store_unit_absent_%d.cfg" % (Time.get_ticks_msec()) DirAccess.remove_absolute(ProjectSettings.globalize_path(absent_path)) # 确保无文件 var d = StoreScript.new(absent_path) @@ -55,7 +55,7 @@ func _test_settings_default() -> void: # ---- 引擎档应用链(reduced→apply()→UiKit.motion_scale==0.5,false→1.0;测毕还原静态初值)---- func _test_settings_apply_chain() -> void: -# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4(应用链组) +# oracle: design/gdd/systems/表现与支撑/设置与本地化.md:4 (应用链组) var saved_scale: float = UiKit.motion_scale # 静态初值留底——测毕还原 var a = StoreScript.new(ISO_PATH) a.reduced_motion = true diff --git a/game/tests/unit/presentation/stage_background_test.gd b/game/tests/unit/presentation/stage_background_test.gd index 4958b7c1..2a473af0 100644 --- a/game/tests/unit/presentation/stage_background_test.gd +++ b/game/tests/unit/presentation/stage_background_test.gd @@ -26,12 +26,12 @@ class AllMissingStage extends BattleStageView: func _initialize() -> void: -# oracle: design/gdd/ux/battle.md:122(舞台背景宿主——装载组) +# oracle: design/gdd/ux/battle.md:122 (舞台背景宿主——装载组) call_deferred("_run") func _run() -> void: -# oracle: design/gdd/ux/battle.md:122(B 区背景宿主组) +# oracle: design/gdd/ux/battle.md:122 (B 区背景宿主组) _test_layout_ground_field() await _test_background_selection() await _test_background_fallback() @@ -42,7 +42,7 @@ func _run() -> void: # ---- V1 ground_h 字段与校验(表值/默认表 validate 空/三态拒绝族) ---- func _test_layout_ground_field() -> void: -# oracle: design/gdd/ux/battle.md:122(连续地面层组) +# oracle: design/gdd/ux/battle.md:122 (连续地面层组) var layout = load("res://data/tables/battle_layout.tres") Kit.check("布局表 ground_h = 100 且默认表 validate 为空〔舞台背景 V1〕", int(layout.ground_h) == 100 and layout.validate().is_empty()) @@ -75,7 +75,7 @@ func _make_stage() -> Node: # ---- V2 背景按 board_type 选图(直建默认 dungeon + 三值路径逐值对应 + 层序在世界之下) ---- func _test_background_selection() -> void: -# oracle: design/gdd/ux/battle.md:122(三关变体选图组) +# oracle: design/gdd/ux/battle.md:122 (三关变体选图组) var layout = load("res://data/tables/battle_layout.tres") var stage: Node = _make_stage() await process_frame @@ -104,7 +104,7 @@ func _test_background_selection() -> void: # ---- V3 缺图回退(目标缺→dungeon;全缺→平色兜底不崩不刷产品日志) ---- func _test_background_fallback() -> void: -# oracle: design/gdd/ux/battle.md:122(缺位回退组) +# oracle: design/gdd/ux/battle.md:122 (缺位回退组) var s1: Node = MissingUnderdarkStage.new(null, load("res://data/tables/battle_layout.tres")) root.add_child(s1) await process_frame @@ -128,6 +128,7 @@ func _test_background_fallback() -> void: # ---- V4 连续地面层几何(底贴 B 区底、高读表、全宽、位于背景与世界之间) ---- func _test_ground_layer_geometry() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var layout = load("res://data/tables/battle_layout.tres") var stage: Node = _make_stage() await process_frame @@ -154,6 +155,7 @@ func _test_ground_layer_geometry() -> void: # ---- V5 光柱层与舞台底源常量退役(源码锚 + 常量表 + 菜单斜光带在位) ---- func _test_beams_retired() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var f := FileAccess.open("res://presentation/views/battle_stage_view.gd", FileAccess.READ) var src := f.get_as_text() if f != null else "" Kit.check("舞台源码零光柱残留且平色回退含合并目标 SLOT_BACKDROP〔舞台背景 V5〕", diff --git a/game/tests/unit/presentation/standee_fx_sweep_test.gd b/game/tests/unit/presentation/standee_fx_sweep_test.gd index ad5a9cd3..47a665ae 100644 --- a/game/tests/unit/presentation/standee_fx_sweep_test.gd +++ b/game/tests/unit/presentation/standee_fx_sweep_test.gd @@ -69,7 +69,7 @@ class FakeDB: func _init() -> void: -# oracle: design/gdd/ux/battle.md:154(站桩呈现域缺陷扫掠组) +# oracle: design/gdd/ux/battle.md:154 (站桩呈现域缺陷扫掠组) UiKit.motion_scale = 1.0 # ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)---- diff --git a/game/tests/unit/presentation/standee_present_values_test.gd b/game/tests/unit/presentation/standee_present_values_test.gd index 35e1e589..0d64a4d5 100644 --- a/game/tests/unit/presentation/standee_present_values_test.gd +++ b/game/tests/unit/presentation/standee_present_values_test.gd @@ -31,7 +31,7 @@ func _read_source(rel_path: String) -> String: func _init() -> void: -# oracle: design/gdd/ux/battle.md:154(站桩呈现定值与素材绑定组) +# oracle: design/gdd/ux/battle.md:154 (站桩呈现定值与素材绑定组) # ---- V5 血条填充 = 危险红 #A8321F(§4.2 语义物件显式落法) ---- Kit.check("血条填充常量 = 危险红 #A8321F〔样张批 V5〕", UiKit.HP_BAR_FILL.to_html(false) == "a8321f") @@ -137,7 +137,7 @@ func _enemy_tex_path(key: String) -> String: func _check_display_tier() -> void: -# oracle: design/gdd/ux/battle.md:154(显示档与分辨率绑定组) +# oracle: design/gdd/ux/battle.md:154 (显示档与分辨率绑定组) # V13/V15 九键逐一:真实入口 StandeeBody → set_data(enemy_id) → set_portrait_texture(真纹理) → portrait_probe for key: String in _DISPLAY_SPEC: var spec: Array = _DISPLAY_SPEC[key] diff --git a/game/tests/unit/presentation/terrain_tile_sets_test.gd b/game/tests/unit/presentation/terrain_tile_sets_test.gd index 706c7155..71519fde 100644 --- a/game/tests/unit/presentation/terrain_tile_sets_test.gd +++ b/game/tests/unit/presentation/terrain_tile_sets_test.gd @@ -13,7 +13,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(地形瓦片套宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (地形瓦片套宿主——装载组) var board_dir := ProjectSettings.globalize_path("res://assets/art/board/") var probe_path := board_dir.path_join("terrain_wall_center_dungeon.png") if not FileAccess.file_exists(probe_path): @@ -42,7 +42,7 @@ func _init() -> void: # 套完整性:词表每个 board_type 的 16 墙 + 5 地面变体全部在盘(缺位 → BoardView 整批回落 dungeon 的 # 缺位回退断言由 ui_input_probe 覆盖;本断言管「入包完整」面) func _test_set_completeness(board_dir: String, types: Array) -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(瓦片套完整性组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (瓦片套完整性组) var wall_names: Array = TilePaletteScript.TILE_WALL_NAMES var variants: int = int(TilePaletteScript.TILE_FLOOR_VARIANTS) for bt in types: @@ -124,7 +124,7 @@ func _load_stats(board_dir: String, fname: String, top_half: bool) -> Dictionary # (双峰纹理直画不可救——渲染帧取证深墨隐形实证,牌上深墨 12:1);③直画模式的双门槛值照抄 # TilePalette 判据常量(防判据漂移) func _test_digit_ink_modes(board_dir: String, types: Array) -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(数字墨色模式组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (数字墨色模式组) var expect_plate := {"dungeon": false, "underdark": true, "deep_underdark": true} var variants: int = int(TilePaletteScript.TILE_FLOOR_VARIANTS) for bt in types: diff --git a/game/tests/unit/presentation/theme_projection_test.gd b/game/tests/unit/presentation/theme_projection_test.gd index d18f3b48..f5e2bdda 100644 --- a/game/tests/unit/presentation/theme_projection_test.gd +++ b/game/tests/unit/presentation/theme_projection_test.gd @@ -30,7 +30,7 @@ var _rng func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(呈现投影与主题宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (呈现投影与主题宿主——装载组) _db = ConfigDBScript.new() # 表加载失败不中止(承 G2–G5 同款:表损坏的红落在具体断言上) var load_ok: bool = _db.load_tables("res://data/tables", true) @@ -181,7 +181,7 @@ func _meta_new_save(path: String): # ---- 未开格类型微光(作用集恒等/九型归组封闭/零玩法写入)---- func _smoke_type_glow() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(未开格类型微光数据面组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267 (未开格类型微光数据面组) print("--- 未开格类型微光(作用集恒等/九型归组封闭/零玩法写入)---") var board_generator = load("res://core/board_generator.gd").new() var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db) @@ -383,7 +383,7 @@ func _smoke_type_glow() -> void: # _format_row Invalid access 使结算段中途夭折、界面卡死。行协议变更必须同步呈现层〔战斗结算 §行协议〕, # 本段把现行行形态逐型喂进真实视图:拆行残留与行字段错位在此转红)---- func _smoke_battle_log_view() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(明细带行协议组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (明细带行协议组) print("--- 战斗日志视图行格式化(行协议 9 类呈现层同步闸)---") var view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout) # 敌方行动伤害行(现行形态:baseline 在场、attach 已出协议——修复前此行即 Invalid access) @@ -428,7 +428,7 @@ func _smoke_battle_log_view() -> void: # ---- 开场行后缀尾接协议(迭代36 wc1 静态半——level_open_line 第五参布尔直控尾接,签名零变; # 该函数此前零既有断言,尾接行为由此钉住)---- func _smoke_level_open_line() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272(关卡开启行组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:272 (关卡开启行组) var NarratorScript := load("res://presentation/battle_narrator.gd") Kit.check("开场行 boosted=true 尾接「(含面板加成)」〔wc1〕", String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)")) @@ -443,7 +443,7 @@ func _smoke_level_open_line() -> void: # V1 挂载覆盖 / V2 滑轨两段 / V3 预盖件运行时证词 / V4 旧形态零残留 / # V5 回归(本冒烟全绿即回归网)/ V6 中文字体防御(挂主题后 default_font 顶掉 custom_font 也不丢字形) func _smoke_global_theme() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(全局主题组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (全局主题组) print("--- UI 全局主题(main_theme.tres 挂载与词条断言)---") var th := ThemeDB.get_project_theme() Kit.check("V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)", th != null) @@ -548,7 +548,7 @@ func _smoke_global_theme() -> void: # ---- 按钮五态矩阵冒烟(四工厂产物逐实例 override 断言;V1/V2)---- func _smoke_button_five_states() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(按钮五态组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (按钮五态组) print("--- UI 按钮五态矩阵(四工厂五态样式盒)---") var uikit_s = load("res://presentation/views/ui_kit.gd") var states := ["normal", "hover", "pressed", "disabled", "focus"] @@ -620,7 +620,7 @@ func _smoke_button_five_states() -> void: # ---- 全局纹理过滤冒烟(工程设置 Nearest+平滑渐变件 LINEAR 白名单;V3)---- func _smoke_texture_filter() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(纹理过滤白名单组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (纹理过滤白名单组) print("--- UI 全局纹理过滤(Nearest+白名单三件)---") var v = ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter") Kit.check("V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)", @@ -670,7 +670,7 @@ func _smoke_texture_filter() -> void: # ---- <4.5:1 文字位换墨冒烟(站桩徽标两族亮档+RAP 行深石墨;V4)---- func _smoke_contrast_inks() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(对比度墨色锚组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281 (对比度墨色锚组) print("--- UI 残余 <4.5:1 文字位换墨(V4 对比度锚定)---") var uikit_s = load("res://presentation/views/ui_kit.gd") var stage: Color = uikit_s.SLOT_BACKDROP # 舞台底对比锚改读合并目标(art-bible §4.1 换色合并③——阈值与判据不变) @@ -697,7 +697,7 @@ func _smoke_contrast_inks() -> void: # ---- 元素细节余②③冒烟(BCard 图标槽明度差降档+商店居中+营地四栏等高)---- func _smoke_element_details() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(元素细节源码锚组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 (元素细节源码锚组) print("--- UI 元素细节余②③(图标槽降档+布局修)---") var uikit_s = load("res://presentation/views/ui_kit.gd") # ②白图标 ≥3:1 图形对比硬约束+降档方向锚定 @@ -720,7 +720,7 @@ func _smoke_element_details() -> void: # ---- 诊断转储钮冒烟(角落钮不在玩家画面+转储入口在调试浮窗;V6)---- func _smoke_diag_button_retired() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260(诊断钮退役契约组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:260 (诊断钮退役契约组) print("--- UI 诊断转储钮退役(V6:玩家画面清零+浮窗可达)---") var main_src: String = FileAccess.get_file_as_string("res://presentation/main.gd") Kit.check("V6 主区无 build_diag_button 调用残留(旧实现同批清理)", @@ -744,7 +744,7 @@ func _smoke_diag_button_retired() -> void: # 断言归 ui_struct_probe 呈现侧,本段只管数据面;跨关解锁概览断言族随面板换代退役—— # 替换判据=sv5 零旧段+八键投影断言(实现报告 §8.3 申报))---- func _status_panel_projection() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274(状态面板只读投影组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:274 (状态面板只读投影组) print("--- 状态面板·本局信息面板投影一致性(迭代36 卡③——八键快照手拼夹具,同构口径不依赖 runner)---") _meta_smoke_cleanup() # 套件级夹具清场(生涯页 fixture 依赖此前置——旧状态段同款保留) # 夹具(代拍板 4——任意非零可辨值,禁读 data/tables 现值、禁与属性行断言共值致误判掩盖) @@ -805,7 +805,7 @@ func _all_labels(root: Node) -> Array: # 只读性连接巡检与零动效断言归 ui_struct_probe 呈现侧,本段只管数据面+旧档载入显示 # (v5 样档升 v6——完成定义 6「计数域缺省补零、页正常显示」))---- func _career_overlay_projection() -> void: -# oracle: design/gdd/ux/camp.md:45(生涯页只读投影组) +# oracle: design/gdd/ux/camp.md:45 (生涯页只读投影组) print("--- 生涯页·只读投影一致性(隔离档 smoke_meta_career/career_v5)---") var sv = _meta_new_save(_META_SMOKE_PATHS[9]) var mp = load("res://core/meta_progression.gd").new(_db, sv) diff --git a/game/tests/unit/presentation/tune_sim_batch_parity_test.gd b/game/tests/unit/presentation/tune_sim_batch_parity_test.gd index b44b9c6f..5fff1de1 100644 --- a/game/tests/unit/presentation/tune_sim_batch_parity_test.gd +++ b/game/tests/unit/presentation/tune_sim_batch_parity_test.gd @@ -163,6 +163,7 @@ func _run_pair(n: int, frame_idx: Dictionary) -> Dictionary: func _check_n3(r: Dictionary) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var pa: FakePanel = r["pa"] var pb: FakePanel = r["pb"] var da: FakeDriver = r["da"] @@ -195,6 +196,7 @@ func _check_n3(r: Dictionary) -> void: func _check_n0(r: Dictionary) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var pa: FakePanel = r["pa"] var pb: FakePanel = r["pb"] var da: FakeDriver = r["da"] diff --git a/game/tests/unit/presentation/type_glow_test.gd b/game/tests/unit/presentation/type_glow_test.gd index b4fc4bb7..7a0e9dd5 100644 --- a/game/tests/unit/presentation/type_glow_test.gd +++ b/game/tests/unit/presentation/type_glow_test.gd @@ -46,7 +46,7 @@ const CONST_NAME := { func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267(微光数据面宿主——装载组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:267 (微光数据面宿主——装载组) _test_grouping_closed() _test_frozen_hue_face() _test_render_params() @@ -57,6 +57,7 @@ func _init() -> void: # ① 归组封闭:九值全域映射+清单外显式失败+禁子串+两带差异锚定 func _test_grouping_closed() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) for t in TYPE_EXPECT: var got: int = UiKitScript.glow_group_for_cell_type(t) Kit.check("九型封闭:%s → 组%d(期望组%d)" % [t, got, int(TYPE_EXPECT[t])], got == int(TYPE_EXPECT[t])) @@ -80,6 +81,7 @@ func _test_grouping_closed() -> void: # ② 冻结色相面:逐组 HSV 对照 art-bible §4.2 冻结值——只准动明度(ΔH≤0.2°/ΔS≤0.01) func _test_frozen_hue_face() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) for g in FROZEN: var f: Color = FROZEN[g] var m: Color = IMPL[g] @@ -98,6 +100,7 @@ func _test_frozen_hue_face() -> void: # ③ 呈现参数锚定(art-bible §4.2 v2 冻结参数) func _test_render_params() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) Kit.check("泛光强度 α = 170/255(v2 呈现参数冻结)", is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0)) Kit.check("带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)", @@ -109,6 +112,7 @@ func _test_render_params() -> void: # ④ 合成空间阶梯:模型域单调 + 相邻组 CR ≥1.20(色弱互辨数据面预判;终审=探针实测) func _test_gamma_ladder() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) # 合成模型(ui_kit 头注登记口径):带内均值 ≈ 0.49×色值 + 0.51×瓦片(BRICK 未开掩尘地砖) var k := 0.49 var tile: Color = UiKitScript.BRICK @@ -135,6 +139,7 @@ func _test_gamma_ladder() -> void: # ⑤ probe API 形状:默认开+空态键集(无板直取——不依赖棋盘装配) func _test_probe_api_shape() -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var view = BoardViewScript.new() var snap: Dictionary = view.type_glow_probe() Kit.check("probe 键集 = {enabled, cells, errors}", diff --git a/game/tests/unit/presentation/ui_font_asset_test.gd b/game/tests/unit/presentation/ui_font_asset_test.gd index 6e1d7801..d263f554 100644 --- a/game/tests/unit/presentation/ui_font_asset_test.gd +++ b/game/tests/unit/presentation/ui_font_asset_test.gd @@ -11,7 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: -# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281(中文字体随包与字形覆盖组) +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:281 (中文字体随包与字形覆盖组) var path: String = UiKitScript.FONT_UI var f: Font = UiKitScript.load_font(path) Kit.check("随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)", f != null) diff --git a/tools/capability_audit.py b/tools/capability_audit.py index 6cb4b237..721336ad 100644 --- a/tools/capability_audit.py +++ b/tools/capability_audit.py @@ -14,9 +14,14 @@ r"""capability_audit.py — 能力清单、覆盖域表与 GDD 纯度机检( tests//probe/)有 `func 方法(`,或该方法名能在 reports/需求卡片*.md 中找到(计划入口同 D 口径);两头都无=红。 F 覆盖域表:systems 每篇一行;状态∈{已覆盖,部分覆盖,未覆盖};非未覆盖行的「能力行」非空且每项都能 对上能力表里的行名;部分/未覆盖行的缺口非空且指待办清单。 + G 判据出处随测注记(test-conventions §8.3):game/tests/** 与 game/probe/** 内每个验证函数 + (函数体含 check(/check_quiet(/_problems.append(/push_error( 判定调用,定义行与 _check( 前缀除外) + 须带 ≥1 条 `# oracle: :<行号> <判据一句>` 注记;指针指向存在的设计文档行且不出 + design/ 全域。扫描面=game/tests/**+game/probe/**(sim/ 不进——测量面直构惯例)。 条件闸行豁免(代表用例含「条件闸」字样)不参与 D/E。 -自检四道(防扫描面空转假绿):系统文档数、能力行数、manifest 套件数、覆盖表行数任一为零 ⇒ exit 2。 +自检五道(防扫描面空转假绿):系统文档数、能力行数、manifest 套件数、覆盖表行数、判据注记扫描面文件数 +任一为零 ⇒ exit 2。 用法: python tools/capability_audit.py # 报告模式(红项只打印,不改退出码) @@ -332,6 +337,67 @@ def check_coverage(cov_tables, names): red(README.relative_to(REPO), 1, "覆盖域表", "%s 未在覆盖域表登记" % doc) +ORACLE_RE = re.compile(r"^#\s*oracle:\s*(\S+\.md):(\d+)\s+\S") +VERDICT_RE = re.compile(r"(? Date: Fri, 6 Nov 2026 07:42:46 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E6=96=87=E6=A1=A3(=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B0=E6=88=90=E6=96=87+README?= =?UTF-8?q?=E8=A1=8C=E8=AE=A2=E6=AD=A3):=20test-conventions=20=C2=A78.3=20?= =?UTF-8?q?=E6=96=B0=E7=AB=8B=E3=80=8C=E6=B5=8B=E8=AF=95=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=8D=AE=E5=87=BA=E5=A4=84=E6=B3=A8=E8=AE=B0?= =?UTF-8?q?=E3=80=8D=E6=9D=A1=E6=AC=BE(=E6=A0=BC=E5=BC=8F/=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=87=BD=E6=95=B0=E5=AE=9A=E4=B9=89/=E6=8C=87?= =?UTF-8?q?=E9=92=88=E7=A9=BA=E9=97=B4design=E5=85=A8=E5=9F=9F/=E4=B8=8E?= =?UTF-8?q?=E9=93=BE=E5=BC=8F=E9=94=9A=E5=88=86=E5=B7=A5/=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E4=B9=89=E5=8A=A1/=E8=A2=AB=E5=8A=A8=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E5=B1=80=E9=99=90=E6=8A=AB=E9=9C=B2/=E8=A7=84?= =?UTF-8?q?=E6=A0=BC=E7=BC=BA=E5=8F=A3=E5=A4=84=E7=BD=AE)+=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E9=97=B8=E6=B8=85=E5=8D=95=E5=90=8C=E6=AD=A5;=20READM?= =?UTF-8?q?E=E8=83=BD=E5=8A=9B=E8=A1=8C=E5=9B=9B=E8=A1=8C=E9=9A=8F?= =?UTF-8?q?=E6=89=B9=E8=AE=A2=E6=AD=A3(:158=E4=B8=83=E7=89=A9=E4=BB=B6?= =?UTF-8?q?=E6=92=A4=E6=AE=8B=E9=99=A9=E7=99=BB=E8=AE=B0=E6=8D=A2=E7=9C=9F?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=89=BF=E8=BD=BD+:161=E7=AF=9D=E7=81=AB?= =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC+:165=E5=B8=83=E5=91=8A=E6=9D=BF+:17?= =?UTF-8?q?0=E4=BA=94=E7=AA=97=E7=9C=9F=E7=82=B9=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E8=AE=B0);=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20run=5Fall?= =?UTF-8?q?=5Fgates.sh=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(=E5=9F=BA=E7=BA=BF?= =?UTF-8?q?=E4=B8=80=E8=87=B4,=E5=9B=9E=E6=89=A7cba3d647)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/engineering/test-conventions.md | 4 +++- game/tests/README.md | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index 75f8d9aa..3d410951 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -100,6 +100,8 @@ **判据出处行锚(2026-11-05 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改,被编入迭代开工前由写卡段按本粒度补挂)**:对照表「需求点」列每行必须带**判据出处行锚**——该行验收判据所依据的现行文档句子,格式「`<文档相对路径>:<行号>`」。现行文档=`design/` 全域现行文档(含 `ux/` 交互规格与 art-bible 的判据行);已退役的需求卡片不是现行文档,不得充当判据出处。作用=断言预期值从规格句抄出,不从实现反推:实现行为与规格句冲突时断言红;规格缺席的行写不出锚,即停卡退回写卡段(那是需求澄清缺口,不是测试缺口)。机检=`tools/pipeline_gate.py` 判据 C5(锚格式与指向存在性,按卡文件名日期分流)+`tools/capability_audit.py` 链式锚检查(断言→套件→能力行→系统文档,见 §8.9)。 +**测试函数的判据出处注记(2026-11-06 立)**:`game/tests/**` 与 `game/probe/**` 内每个**验证函数**(函数体含 `check(`/`check_quiet(`/`_problems.append(`/`push_error(` 判定调用的函数——定义行与 `_check(` 前缀不作判定计)必须带至少一条**判据出处注记**,格式「`# oracle: :<行号> <判据一句>`」——指针在前、判据摘要句在后,断言预期值的出处随断言走:断言红时读注记即达出处文档句,零查表。指针空间=`design/` 全域现行文档(行为测试锚 owner 系统文档「验收标准」句或 `ux/` 规格句;惯例类测试〔封装扫描、判定通道纪律、驱动契约〕锚规约条款行);`game/sim/` 不进扫描面(测量面直构惯例)。与链式锚的分工:链式锚(断言→套件→能力行→系统文档)不变、属义务清单层;本注记属执行证据层、写在断言文件内,非独立账本(§8.9 法则 4 不变)。写新测试函数时当场挂注记;被指规格句改行号时随批维护指针——死指针即红。局限如实声明:注记是被动文本,句内容改而行数不变时机检抓不到(与卡片判据 C5 同限)。机检=`tools/capability_audit.py` 判据出处注记道(覆盖检查+指针存在性;扫描面零文件自检红)。规格缺席写不出锚=发现规格缺口:记发现不造锚、收尾呈报,禁锚到非现行文档凑数。 + **验收档申报(2026-11-04 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改)**:每张需求卡片在头部信息区(与设计事实源声明同区)申报一个验收档,三档:高/中/低。判据=失败代价单维——这条改动做错了,后果随存档或结算传播的(存档写读与迁移、RNG 流、战斗结算公式与乘区、经济入账、数值表结构校验)=高;后果玩家可见但不损持久状态的(常规玩法逻辑、门控、交互逻辑、呈现数据面)=中;后果即看即知且无状态残留的(文案、纯呈现样式、布局微调)=低。一张卡触多档按最高档申报;申报由写卡段落笔,用户批卡(U0)时确认或改判。档位只调节 §8.7 审计深度——闸机与 CI 的域划分、选闸算法、全量强制档一概不因档位变化,真实入口、判据绑定面、禁削弱等验收闭合判据全部照旧。未申报档位的卡一律按高档全量审计。机检=`tools/pipeline_gate.py` 判据 C4:卡内须含「验收档:高/中/低」申报行。 **能力清单维护锚(2026-10-29 用户裁改版定稿)**:`game/tests/README.md`「能力清单」是**实现指导+阅读导航+义务声明**——只宣布「要验证什么」,不记「是否已验证/是否跑过」;落地账归现行验收闭合链(改动—验收对照表逐条挂断言+合回前独立验收审计),本清单不承载验证状态。表为四列:能力|系统|真实入口|代表用例(`文件:函数`)。**维护归写卡段,当场入表**:写需求卡片时,本批新增能力当场写入能力行——真实入口取自设计文档已定的入口,代表用例=卡里定死的计划测试(`文件:函数`);受影响存量行(入口/用例名变更)同批更新;能力退役删行。**实现照单落名**:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。**收尾核对**:批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段;实现批禁写该文件的现行红线不变(process-conventions §3 第 11 条),核对义务归收尾提交。三层模型见 §8.9。改动—验收对照表纪律不变:新增或改写的每条断言仍须挂一行(上文「测试侧」);断言改判仍受 §8.4 真实入口口径约束。 @@ -175,4 +177,4 @@ **文档纯度**:验收意图与能力描述只写玩家或系统可观测面,禁写代码路径(`.gd`/`.py`/`.sh`)、测试文件名、断言名;唯一例外=配置文件索引(`game/data/tables/*.tres` 这类表路径与表名)。规则正文见 doc-conventions §5「禁实现内容」。 -**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上;红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。 +**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上、判据出处注记覆盖与指针可核(§8.3 测试函数的判据出处注记);红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。 diff --git a/game/tests/README.md b/game/tests/README.md index 62f9242a..270e1c25 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -155,19 +155,19 @@ tests/ | 战斗叙述与终局文案(数值带入/胜负互异) | HUD框架 | `BattleNarrator.battle_end_text()`/`BattleNarrator.boss_name()` | `battle_narrator_test.gd:_test_numeric_lines_carry_values`;`battle_narrator_test.gd:_test_end_text_differs_by_result`;`battle_narrator_test.gd:_test_boss_name_uses_boss_rowset` | | 玩家面文案与危险格格面字形 | HUD框架 | `BoardView.rebuild()`/`StatusSlotRow.set_effects()` | `player_copy_sweep_test.gd:_sweep_board_glyph`;`player_copy_sweep_test.gd:_sweep_legend`;`player_copy_sweep_test.gd:_sweep_hero_panel` | | 营地场景图十层铺底与门洞留空呈现 | 营地场景视图 | 进入存档→选档→进档→营地场景屏(`CampSceneView.setup_layers()` 驱动);单元测试载入 `camp_scene_layout.tres` | `shot_probe.gd:_ready`(camp 截图段);`camp_scene_layout_test.gd:_test_layout_hotspot_geometry` | -| 营地七物件命中区点选与分界面可达 | 营地场景视图 | 七物件命中区 → `CampSceneView._gui_input()`(命中发 `object_pressed` 信号) → 组合根开界面;**现役承载=emit 信号直发,标「接线验证」——真输入承载待建(残险登记,2026-11-05 真实入口口径收窄)** | `ui_click_probe.gd:_ready`(七物件合成点击段) | +| 营地七物件命中区点选与分界面可达 | 营地场景视图 | 七物件命中区 → 合成鼠标事件经 `push_input` 真点(`ProbeUiUtil.push_click_at` 热点中心)→ `CampSceneView._gui_input()`(命中发 `object_pressed` 信号) → 组合根开界面(2026-11-06 真输入化,残险登记撤) | `ui_click_probe.gd:_ready`(七物件合成点击段——真输入驱动) | | 营地大门两分支确认与断点续玩接线 | 营地交互流 | 大门命中区 → 合成鼠标事件经 `push_input` 真点(`ProbeUiUtil.push_click_at`)→ ConfirmDialog 真钮(三出口)→ `main._on_confirm_accepted()`(新冒险确认)/`main._resume_from_menu()`(断点续玩) | `ui_click_probe.gd:_ready`(大门两分支段/断点确认段——真输入 push_click 驱动,2026-11-05 真输入化);`presentation_encapsulation_sweep_test.gd:_init`(迭代52卡③组合根交接面结构性主证——pres封装 V1–V4 四组私取零命中+新公开面正锚,2026-11-05 补链锚) | | 四柜台拆解后五界面入口改挂与写口零回归 | 营地五界面 | 五界面意图信号 → `CampShopPresenter` → `MetaProgression.tree_buy()` 写口 | `forge_workbench_test.gd:_run`(承载类同迁断言十二项);`ui_click_probe.gd:_ready`(写口链段) | -| 营地篝火回主菜单与笔记本改挂动线 | 营地交互流 | 笔记本命中区 → `CareerOverlay.open()`;篝火命中区 → ConfirmDialog 二次确认 → 主菜单 | `ui_struct_probe.gd:_ready`(career 段);`screen_flow_probe.gd:_ready`(篝火/开合段) | +| 营地篝火回主菜单与笔记本改挂动线 | 营地交互流 | 笔记本命中区真点(`push_click_at`)→ `CareerOverlay.open()`;篝火命中区真点 → ConfirmDialog 真钮二次确认 → 主菜单(2026-11-06 真输入化) | `ui_struct_probe.gd:_ready`(career 段);`screen_flow_probe.gd:_ready`(篝火/开合段——真输入驱动) | | 关卡画面视口跟随 | 关卡画面(选路枢纽) | 停点切换与兑付回图经真实屏流程;读面 `LevelMapViewport.viewport_center_canvas_pos()` | `level_map_viewport_probe.gd:_probe_follow_anchor`;`level_map_viewport_probe.gd:_probe_smooth_scroll_reduced_motion` | | 关卡画面视口平移 | 关卡画面(选路枢纽) | 合成拖拽沿与滚轮沿经 `push_input`(`ProbeUiUtil.push_drag_at()`/`push_wheel_at()`) | `level_map_viewport_probe.gd:_probe_drag_pan`;`level_map_viewport_probe.gd:_probe_wheel_pan`;`level_map_viewport_probe.gd:_probe_clamp_bounds_and_columns`;`level_map_viewport_probe.gd:_probe_drag_no_submit` | | 关卡画面视口指引 | 关卡画面(选路枢纽) | 深图经 `LevelMapScreen.open_choices()` 真实停点 | `level_map_viewport_probe.gd:_probe_guidance_chrome` | -| 营地图鉴占位挂起态与反参照结构纪律 | 营地场景视图 | 布告板命中区 → `CampSceneView._gui_input()`(object_pressed 信号) → 图鉴占位视图;营地根态场景树审计 | `ui_struct_probe.gd:_ready`(占位禁词/禁行段);`ui_struct_probe.gd:_ready`(结构禁词段) | +| 营地图鉴占位挂起态与反参照结构纪律 | 营地场景视图 | 布告板命中区真点(`push_click_at`)→ `CampSceneView._gui_input()`(object_pressed 信号) → 图鉴占位视图;营地根态场景树审计(2026-11-06 真输入化) | `ui_struct_probe.gd:_ready`(占位禁词/禁行段);`ui_struct_probe.gd:_ready`(结构禁词段) | | 营地刷新单口与场景根 HUD 同步 | 营地刷新总线 | 写口尾 → `CampRefreshBus.refresh()` → 现开界面刷新+场景根 HUD 刷新 | `ui_struct_probe.gd:_ready`(顶栏位段);`ui_click_probe.gd:_ready`(写口尾刷新段) | | Esc 浮窗唤起与三出口动线 | 营地 Esc 流 | 营地场景根态合成 Esc 键事件→ `main_esc_flow.on_esc_input()` → `ui.flow.open/close`;浮窗三钮 `pressed` → 三出口 | `screen_flow_probe.gd:_ready`(Esc 唤起/子界面开态不唤起/再按关窗/非营地不唤起段);`screen_flow_probe.gd:_ready`(返回动线/设置双路径段) | | 减动效开关存储契约(持久化回读/缺省值/引擎动效档应用) | 设置与本地化 | `SettingsStore.load_settings()`/`save_settings()`/`apply()` | `settings_store_test.gd:_test_settings_roundtrip`;`settings_store_test.gd:_test_settings_default`;`settings_store_test.gd:_test_settings_apply_chain` | | 营地顶栏 HUD 钥石读数与刷新单口同步 | 营地 HUD 栏 | 隔离存档实例 `meta_state._add_keystone()`→ 显式触发 `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` 读数 | `screen_flow_probe.gd:_ready`(HUD 余额读数/刷新段);`ui_struct_probe.gd:_ready`(顶栏位结构段) | -| 营地五窗统一窗壳形态 | 营地五界面 | 七物件命中区/笔记本 → `CampSceneView._gui_input()`(`object_pressed` 信号)→ 组合根分派开窗;窗板几何(居中定尺寸 720×640 起点)/遮罩/窗内滚动面为呈现提交面 | `screen_flow_probe.gd:_step_camp_window_shell`(窗壳形态段) | +| 营地五窗统一窗壳形态 | 营地五界面 | 七物件命中区/笔记本真点(`push_click_at`,2026-11-06 真输入化)→ `CampSceneView._gui_input()`(`object_pressed` 信号)→ 组合根分派开窗;窗板几何(居中定尺寸 720×640 起点)/遮罩/窗内滚动面为呈现提交面 | `screen_flow_probe.gd:_step_camp_window_shell`(窗壳形态段) | **旅程段七:数据配置与跨系统契约** From 193902ad8f473b5e3ad067b5c588c13cccd8f26f Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:43:51 +0800 Subject: [PATCH 18/19] =?UTF-8?q?=E6=94=B6=E5=B0=BE(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=96=E6=89=B9=C2=B7=E5=BE=85=E5=8A=9E=E5=A4=84?= =?UTF-8?q?=E7=BD=AE):=20=E6=B5=8B=E8=AF=95=E9=9D=A2=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=96=E6=9D=A1=E7=9B=AE=E5=AE=8C=E6=88=90=E5=88=A0?= =?UTF-8?q?=E9=99=A4(=E7=9B=B4=E6=94=B9=E5=BA=8F=E5=88=97=E2=80=94?= =?UTF-8?q?=E2=80=947=E6=8E=A2=E9=92=8863=E5=A4=84=E8=BD=AC=E7=9C=9F?= =?UTF-8?q?=E8=BE=93=E5=85=A5+=E5=88=A4=E6=8D=AE=E5=87=BA=E5=A4=84?= =?UTF-8?q?=E6=B3=A8=E8=AE=B0401=E5=87=BD=E6=95=B0=E5=85=A8=E5=BA=93?= =?UTF-8?q?=E9=93=BA=E5=BC=80+capability=5Faudit=20G=E9=81=93=E6=9C=BA?= =?UTF-8?q?=E6=A3=80+=E8=A7=84=E7=BA=A6=E6=88=90=E6=96=87);=20QA=E6=80=BB?= =?UTF-8?q?=E8=A7=88=E9=9D=A2=E6=9D=BF=E6=9D=A1=E7=9B=AE=E5=AE=9E=E5=86=B5?= =?UTF-8?q?=E6=9B=B4=E6=96=B0(=E2=91=A0=E6=89=BF=E6=8E=A5=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=92=A4=E6=AE=8B=E9=99=A9=E5=BC=95=E7=94=A8,?= =?UTF-8?q?=E2=91=A1=E2=91=A2=E4=BB=8D=E5=BE=85=E6=89=A7=E8=A1=8C);=20?= =?UTF-8?q?=E6=96=B0=E7=99=BB=E8=AE=B0=E5=8F=91=E7=8E=B0=E5=9B=9B=E9=A1=B9?= =?UTF-8?q?=E5=BE=85=E8=A3=81=E6=9D=A1(route=E6=97=B6=E5=BA=8F=E5=81=87?= =?UTF-8?q?=E7=BB=BF=E9=9D=A2/=E5=81=9C=E6=89=8B=E5=9B=BE=E4=BE=8B?= =?UTF-8?q?=E5=AE=BD=E5=BA=A6=E8=BF=9D=E8=A7=84/=E5=9B=BE=E4=BE=8B?= =?UTF-8?q?=E7=9C=9F=E8=BE=93=E5=85=A5=E6=89=BF=E8=BD=BD=E5=BE=85=E8=A3=81?= =?UTF-8?q?/tune=5Fsim=E8=A7=84=E6=A0=BC=E7=BC=BA=E5=8F=A3);=20roadmap?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E4=BD=8D=E5=BE=85=E7=BC=96=E6=B1=A0=E6=8D=A2?= =?UTF-8?q?=E4=BB=A3;=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20=E6=B5=81?= =?UTF-8?q?=E6=B0=B4=E7=BA=BF=E9=97=B8PASS(27=E6=9D=A1=E7=9B=AE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roadmap.md | 2 +- 待办清单.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/roadmap.md b/roadmap.md index 91cf5666..df1cf78e 100644 --- a/roadmap.md +++ b/roadmap.md @@ -6,7 +6,7 @@ - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 - **迭代位**:迭代 1–53 全部已完成;各迭代收口与批后收尾沿革查 git 提交信息与 `reports/`。 -- **下一步**:无已编迭代在飞——待编池:效果通道与事件词零现役普查(待写卡)、结算明细文案入表(待文档设计)、二三关棋盘观感(待设计+待美术)、测试面真输入化分期与存量断言逐条精锚(待执行)、QA 覆盖总览面板与自动试玩扩展(待执行);EC8 热重载实装待用户另立条目。 +- **下一步**:无已编迭代在飞——待编池:效果通道与事件词零现役普查(待写卡)、结算明细文案入表(待文档设计)、二三关棋盘观感(待设计+待美术)、测试面真输入化批发现四项待裁(待执行)、QA 覆盖总览面板与自动试玩扩展(待执行);EC8 热重载实装待用户另立条目。 ## 里程碑时间线 diff --git a/待办清单.md b/待办清单.md index d12e6d62..93a0e170 100644 --- a/待办清单.md +++ b/待办清单.md @@ -243,17 +243,17 @@ **状态**:待执行 -#### 测试面真输入化分期与存量断言逐条精锚(2026-11-05 登记,来源=批次移交) +#### 测试面真输入化批发现四项待裁(2026-11-06 登记,来源=批次移交) -**实况**:证据=test-conventions §8.3「判据出处行锚」「真实入口判定口径」两条文(本批落);链式锚机检已在闸(capability_audit 链断 0,80 套件全承接)。剩余面=①营地七物件段等 ~40 处 emit 站点仍为接线验证(大门/进档链已于本批真输入化,能力行 :158 残险在案)②存量 996 条断言的判据出处逐条精锚(链式锚已保可核,精锚提升出处粒度)。 +**实况**:真输入化批(2026-11-06 合入)实现中坐实的发现——①route 选路信号路径屏幕切换时序:ui_struct_probe emit 选路后 battle_root 持续不显示,battle_hud 结构审计长期空转采样(假绿面);真点路径采样变实。②AP 条停手钮(64px)/图例钮(56px) 同容器宽度不等——equal_width 纪律真违规(真点采样变实后暴露)。③图例开启真输入承载待①裁决后补(ui_struct/ui_click 图例段现保留 emit+前提注记)。④tune_sim_batch_parity(调参测量域)无 design/ 规格句可锚——判据出处注记规格缺口(机制事实源=architecture:168,验收句缺席)。证据=worktree test-oracle-anchor 批内提交(git 历史可考)+ui_struct_probe.gd:234 注记。 -**改动面**:游戏代码 +**改动面**:游戏代码+文档 **状态**:待执行 #### QA 覆盖总览面板与自动试玩扩展——自动发现测试验证缺漏(2026-11-05 登记,来源=会话提及) -**实况**:方案全文=`D:\code\qa-harness-discussion\QA-Harness讨论结果-中间报告.md`(三批定案,全部三关试点);证据=真实入口缺口在册:能力行「营地七物件命中区点选与分界面可达」残险行 game/tests/README.md:158(emit 直发标「接线验证」、真输入承载待建)+test-conventions §8.3 真实入口判定口径条款(2026-11-05 收窄)。①探针存量 emit 真输入化(~40 处站点,承接上条)②B 覆盖总览面板+本体枚举器(六源回执明细化+能力清单提取+HTML 双击即开+游戏内容↔测试验证对账)③C 自动试玩扩展(auto_player 扩全动线全功能,异常上报含机器自填归因)。裁决在案勿重提:A 执行覆盖矩阵不做、D 缺口闸不做、变异测试禁入、归因登记不立独立制度。 +**实况**:方案全文=`D:\code\qa-harness-discussion\QA-Harness讨论结果-中间报告.md`(三批定案,全部三关试点);证据=test-conventions §8.3 真实入口判定口径条款+「测试函数的判据出处注记」条款(2026-11-06 落文,注记格式已全库铺开——总览面板可扫 `# oracle:` 注记出表)。①探针存量 emit 真输入化已完成(2026-11-06 真输入化批,7 探针 63 处转真输入,README :158 残险撤)②B 覆盖总览面板+本体枚举器(六源回执明细化+能力清单提取+HTML 双击即开+游戏内容↔测试验证对账)③C 自动试玩扩展(auto_player 扩全动线全功能,异常上报含机器自填归因)。裁决在案勿重提:A 执行覆盖矩阵不做、D 缺口闸不做、变异测试禁入、归因登记不立独立制度。 **改动面**:游戏代码+工具代码+文档 From 03ebb4741d6dce3af95225e549c78807b8b8b4fa Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 6 Nov 2026 07:57:42 +0800 Subject: [PATCH 19/19] =?UTF-8?q?=E6=94=B6=E5=B0=BE(=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=96=E6=89=B9=C2=B7=E5=90=88=E5=B9=B6=E5=90=8E?= =?UTF-8?q?=E5=BE=85=E5=8A=9E=E5=AF=B9=E8=B4=A6):=20=E7=9C=9F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=8C=96=E6=9D=A1=E7=9B=AE=E5=AE=9E=E5=86=B5=E6=8C=89?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=90=8E=E7=8A=B6=E6=80=81=E6=8D=A2=E4=BB=A3?= =?UTF-8?q?(=E4=B8=A4=E6=89=B9=E5=90=88=E8=AE=A151+1=E4=B8=8E63=E5=A4=84+?= =?UTF-8?q?=E6=B3=A8=E8=AE=B0402=E5=87=BD=E6=95=B0,=E5=89=A9=E4=BD=99?= =?UTF-8?q?=E9=9D=A2=3D=E8=A7=86=E5=9B=BE=E5=B1=82=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E9=9D=A2);=20=E6=96=B0=E7=99=BB=E8=AE=B0=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E5=9B=9B=E9=A1=B9=E5=BE=85=E8=A3=81=E6=9D=A1(route=E6=97=B6?= =?UTF-8?q?=E5=BA=8F=E5=81=87=E7=BB=BF=E9=9D=A2/=E5=81=9C=E6=89=8B?= =?UTF-8?q?=E5=9B=BE=E4=BE=8B=E5=AE=BD=E5=BA=A6=E8=BF=9D=E8=A7=84/?= =?UTF-8?q?=E5=9B=BE=E4=BE=8B=E6=89=BF=E8=BD=BD=E5=BE=85=E8=A3=81/tune=5Fs?= =?UTF-8?q?im=E8=A7=84=E6=A0=BC=E7=BC=BA=E5=8F=A3);=20roadmap=E5=BE=85?= =?UTF-8?q?=E7=BC=96=E6=B1=A0=E5=8E=BB=E5=B7=B2=E5=AE=8C=E6=88=90QA?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=9D=A1(master=E4=BE=A7=E5=88=A0=E6=9D=A1?= =?UTF-8?q?=E5=90=8E=E9=81=97=E7=95=99=E5=A4=B1=E6=AD=A5=E9=A1=BA=E6=89=8B?= =?UTF-8?q?=E6=B6=88);=20=E6=9C=AC=E6=AD=A5=E5=87=AD=E6=8D=AE:=20=E6=B5=81?= =?UTF-8?q?=E6=B0=B4=E7=BA=BF=E9=97=B8PASS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roadmap.md | 2 +- 待办清单.md | 37 ++++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/roadmap.md b/roadmap.md index df1cf78e..a3e25d20 100644 --- a/roadmap.md +++ b/roadmap.md @@ -6,7 +6,7 @@ - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 - **迭代位**:迭代 1–53 全部已完成;各迭代收口与批后收尾沿革查 git 提交信息与 `reports/`。 -- **下一步**:无已编迭代在飞——待编池:效果通道与事件词零现役普查(待写卡)、结算明细文案入表(待文档设计)、二三关棋盘观感(待设计+待美术)、测试面真输入化批发现四项待裁(待执行)、QA 覆盖总览面板与自动试玩扩展(待执行);EC8 热重载实装待用户另立条目。 +- **下一步**:无已编迭代在飞——待编池:效果通道与事件词零现役普查(待写卡)、结算明细文案入表(待文档设计)、二三关棋盘观感(待设计+待美术)、测试面真输入化批发现四项待裁(待执行);EC8 热重载实装待用户另立条目。 ## 里程碑时间线 diff --git a/待办清单.md b/待办清单.md index 93a0e170..0ba4a77c 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,14 +2,6 @@ 本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 -#### 三关色相 × 五组微光互辨抽测——三关图类型色板终值冻结的前置抽测(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/` 齐备,可开跑。 - -**改动面**:文档+美术素材+工具代码 - -**状态**:美术设计已闭合+待执行 - #### 数值标定窗口总挂账——原九条数值类条目合并(2026-10-28 登记,来源=会话提及) **实况**:九条数值类挂账合并一条(削弱档位终值/迭代 10 奖励重构联动/调参点评登记/三方向解锁清单与定价/GP 经济换算失衡/天赋树欠账余两件/商店刷新两参数/基线通关率越带杠杆扫描/成长系数节奏),逐条明细与统一执行形态(窗口内一次 MC 批量覆盖全冒险期望+奖励期望)全载 reports/汇总-数值标定窗口挂账-2026-10-28.md。共同裁定:未设计不启动,重启条件=用户发令开数值标定窗口。MC 档位硬闸见 sim-data/README.md「MC 采集档位表」。 @@ -243,21 +235,29 @@ **状态**:待执行 +#### 测试面真输入化分期与存量断言逐条精锚(2026-11-05 登记,来源=批次移交) + +**实况**:证据=test-conventions §8.3「判据出处行锚」「真实入口判定口径」两条文+§2「UI 驱动断言须走真输入管线」条文(批1补,qa-harness 分支 2cbd2ca9)+「测试函数的判据出处注记」条款与机检 G 道(2026-11-06 批,capability_audit --strict 绿 402 函数)。已承接=探针 emit 站点两批合计(QA 批两探针 51+1 与真输入化批七探针 63 处,8 处在案保留带注记)+判据出处注记全库铺开(402 验证函数挂 `# oracle:` 指针)。剩余面=视图层测试方法面(press_candidate/press_continue/press_card/press_confirm/press_refresh_for_probe 等探针消费口,非 emit 站点)。 + +**改动面**:游戏代码 + +**状态**:待执行 + #### 测试面真输入化批发现四项待裁(2026-11-06 登记,来源=批次移交) -**实况**:真输入化批(2026-11-06 合入)实现中坐实的发现——①route 选路信号路径屏幕切换时序:ui_struct_probe emit 选路后 battle_root 持续不显示,battle_hud 结构审计长期空转采样(假绿面);真点路径采样变实。②AP 条停手钮(64px)/图例钮(56px) 同容器宽度不等——equal_width 纪律真违规(真点采样变实后暴露)。③图例开启真输入承载待①裁决后补(ui_struct/ui_click 图例段现保留 emit+前提注记)。④tune_sim_batch_parity(调参测量域)无 design/ 规格句可锚——判据出处注记规格缺口(机制事实源=architecture:168,验收句缺席)。证据=worktree test-oracle-anchor 批内提交(git 历史可考)+ui_struct_probe.gd:234 注记。 +**实况**:真输入化批实现中坐实——①route 选路信号路径屏幕切换时序:ui_struct_probe emit 选路后 battle_root 持续不显示,battle_hud 结构审计长期空转采样(假绿面)。②AP 条停手钮(64px)/图例钮(56px) 同容器宽度不等——equal_width 纪律真违规。③图例开启真输入承载待①裁决(两探针图例段保留 emit+前提注记)。④tune_sim_batch_parity(调参测量域)无 design/ 规格句可锚——注记规格缺口(机制事实源=architecture:168)。证据=test-oracle-anchor 分支批内提交(git 历史可考)+ui_struct_probe.gd 前提注记。 **改动面**:游戏代码+文档 **状态**:待执行 -#### QA 覆盖总览面板与自动试玩扩展——自动发现测试验证缺漏(2026-11-05 登记,来源=会话提及) +#### QA harness 三批缺陷与覆盖遗漏收口(2026-11-06 登记,来源=批次移交,优先级=P0) -**实况**:方案全文=`D:\code\qa-harness-discussion\QA-Harness讨论结果-中间报告.md`(三批定案,全部三关试点);证据=test-conventions §8.3 真实入口判定口径条款+「测试函数的判据出处注记」条款(2026-11-06 落文,注记格式已全库铺开——总览面板可扫 `# oracle:` 注记出表)。①探针存量 emit 真输入化已完成(2026-11-06 真输入化批,7 探针 63 处转真输入,README :158 残险撤)②B 覆盖总览面板+本体枚举器(六源回执明细化+能力清单提取+HTML 双击即开+游戏内容↔测试验证对账)③C 自动试玩扩展(auto_player 扩全动线全功能,异常上报含机器自填归因)。裁决在案勿重提:A 执行覆盖矩阵不做、D 缺口闸不做、变异测试禁入、归因登记不立独立制度。 +**实况**:证据=reports/QA-harness三批-缺陷与覆盖遗漏报告-2026-11-06.md(八类逐条带文件行号与登记处指针:产品缺陷 2=A1 开局被开始层盖住/A2 终局关闭路径引擎报错噪声;测试覆盖遗漏 6=B1 停手钮零真输入/B2 视图方法面/B3 精锚/B4 本体缺口 212 条/B5 控件零命中缺口/B6 判据收紧轮);来源批次=合并 d3c85390(三批读数在案)。处置分流:A1 随营地交互修复批合回即消;A2/B1/B2 走需求性工程写卡通道;B3/B6 归衔接条目与复核轮;B4/B5 人工复核后分流(真缺口立事项/假缺口豁免清单核销)。 -**改动面**:游戏代码+工具代码+文档 +**改动面**:游戏代码 -**状态**:待执行 +**状态**:待写卡 #### 闸机超时守卫缺陷修复——gate_run_timeout 标志文件恒真(2026-11-06 登记,来源=批次移交) @@ -274,3 +274,14 @@ **改动面**:游戏代码 **状态**:待执行 + +#### 美术圣经去数值化——定值改由机检件与数据表承载(2026-11-06 登记,来源=会话提及) + +**实况**:本条立文档分层规矩——美术圣经与 GDD 同级属 Meta 层,只写概念与方向(可写「两者存在相对亮度关系」),不写具体数值约束;阈值与准入判定归执行件,执行件登记面=[art-pipeline-tools](design/art/art-pipeline-tools.md)(三关色板互辨抽测件本批已入册)。 +- 相关文档:纪律出处=[doc-conventions §5 禁实现内容行](design/engineering/doc-conventions.md);同款先例=[08-Alpha版本标准](design/gdd/08-Alpha版本标准.md) 二节「面值一律不在此抄录,以数据表为准」 + +**缺口**:还差一次清点与归属设计讨论——圣经全部定值逐条定落点(机检件/代码常量/数据表/golden 基线)、定文档侧指针句式、核下游反向引用面(ux 文档、需求卡片、探针注引圣经定值之处)。 + +**改动面**:文档+工具代码 + +**状态**:待文档设计