Compare commits

...
Author SHA1 Message Date
DKChan 5a113331b6 卡④T6+卡⑤T4文档尾笔(迭代47): 实现报告+反哺点清单——实际对照表三卡全量/迁移留痕表/实现决定记录十项/反哺点十二条;独立验收审计待派(合回前置) 2026-11-01 18:54:09 +08:00
DKChan 4e4232d804 卡⑤T1-T3(迭代47·Esc浮窗与营地顶栏批): Esc浮窗三钮+MainEscFlow四态路由+CampHudBand顶栏栏+设置语境分支+断言批——终态六枚落满
- 三新件: camp_esc_overlay(modal_shell+三钮,退出=danger_button,零动效零新素材)/main_esc_flow(静态承接件零成员态:is_camp_context+on_esc_input四态路由+_other_modal_open登记册集合查询+三出口动线)/camp_hud_band(48px浮层+keystone_badge,refresh_camp=bus目标册契约面)
- 接线: ui_registry contract+node()第六槽(营地层与设置层之间——契约3栈语义正解);ui_camp_modals六枚落满(卡⑥终态);main_ui_build实例化+三出口薄委托接线+bus目标册纳入HUD栏;main.gd _unhandled_input(契约9接线定稿:KEY_ESCAPE配physical/pressed非echo/禁轮询/不挂ui_cancel)+三薄委托零新增成员;presenter.open_camp尾挂刷新单口(契约5时机②);main_overlay_flow._on_settings_closed语境分支(谓词=esc_overlay.visible谁开谁收)
- 契约9分支序修正: ①先于②(设置开在浮窗之上态先命中无反馈——浮窗不隔层关闭;首装②先被实机场景证伪,孤检2红溯源后按卡面编号归位)
- 断言批: screen_flow Esc步十断言(根态唤起/再按关窗/echo零响应/子界面无反馈/HUD直写显式触发9999/设置开态不误关/关设置回浮窗/返回动线四可见性/菜单语境不唤起)+ui_struct浮窗段(三钮仅三/零动效双件源码+子树AnimPlayer=0/debug_badge零波及)
- 验证: 冒烟PASS+system_test 69项0失败+七探针全绿(screen_flow/ui_struct含Esc段/ui_click问题=0/ui_probe/ui_contrast36对/shot);S9/S10/S13归基线
2026-11-01 18:50:00 +08:00
DKChan 52296872e3 卡④T5补(迭代47): 四子界面内容回包PANEL_DATA米板身+HEADER_BAND死常量摘除——ui_contrast令牌核签随件(上笔T5漏add的三件) 2026-11-01 18:34:50 +08:00
DKChan 1243ffe632 卡④T5(迭代47·营地场景图批): 断言探针截图迁移批——F8全量位点逐点改指四子界面/场景根+大门两分支断言段落位+场景层序段+camp截图换场景图形态
- 全仓旧句柄引用清零(modals.camp.overlay/camp_overlay_view 残留=0): screen_flow(旗标+回退段改篝火动线)/ui_probe(余额徽+树钮改指tree/秘宝行treasure/场景点选路由)/ui_click(树链treasure+灰字按视图分扫treasure/codex锚订正去『柜台』+savesel三处场景根+大门两分支断言段V3落位:无断点两出口cancel确认/有断点三出口set_breakpoint注入次确认开局)/ui_struct(audit根场景图+源扫描改六件+场景层序段V1①子节点序与贴图矩形逐值+锻炉可达V2+顶栏位V8+根态反参照V9+生涯段改笔记本真实路由营地语境分支)/ui_contrast(令牌表核签:头带两行+铭牌副题+最近冒险四行随形态注销——CAMP_HEADER_BAND零消费随退/ROW_PLATE行仍消费保留;四子界面内容回包PANEL_DATA米板身)/shot_probe(camp截图=场景图形态V1载体+收闭改篝火动线)
- CampSceneView补_init visible=false(登记模态初始隐——boot态场景根盖菜单三红同源修复:V1a/V2/V3g career+screen_flow boot互斥)
- 旧形态断言注销(test-conventions §5.5+卡面削弱申报授权):已在T3同批(theme_projection两段);system_test V3g camp分支换代自适配(开=场景根/合=on_camp_closed关层收闭)
- 验证: system_test 69项0失败+六探针全绿(screen_flow六步/ui_probe/ui_struct含新增段/ui_contrast/ui_click问题=0全链含大门两分支与树购买持久/shot 问题=0)+camp截图实拍=场景图形态(门洞透路七物件就位)
2026-11-01 18:25:41 +08:00
DKChan 5a0e7069c1 卡④T4(迭代47·营地场景图批): 大门两分支+ConfirmDialog三钮化+三钮消费方编排+篝火二次确认动线——点大门永远先出确认/cancelled恒无变更退回营地
- ConfirmDialog三钮化(契约3/Q5甲): open_three增设次钮+secondary_confirmed信号——双钮调用方零改动(open隐藏次钮);钮序=[取消][次选项][确认]
- 大门两分支(裁决5): 有断点=三出口弹窗(继续上一段冒险/开始新冒险/取消,open_three)+无断点=是否开始新冒险(是/否);点大门永远先出确认不直接进冒险;cancelled恒=无变更退回营地
- 消费方编排扩展(卡③契约3机制零重构): _confirm_consumer新增gate/gate_new/campfire_leave三消费方+_on_confirm_secondary次确认分派——继续接现役续玩编排(_resume_from_menu同源)/开始接现役开局编排(_start_adventure同源)/冒险发起后营地场景屏隐藏(presenter.on_camp_closed关层收闭=营地与选档层同收+开始层恢复,T3已建);main_ui_build补secondary_confirmed接线
- 篝火动线: 二次确认(camp.md v2.2裁定走ConfirmDialog先例)→确认离场=on_camp_closed;命中只火堆已随T2布局表(560,640,160,80)
- _refresh_resume_button不复用(随卡②删——大门两分支直读has_breakpoint无钮可刷);终局落点不变量T0互证=现役终局continue回主菜单不改
- main.gd零新增成员(分派扩match臂+新方法,15/15保持)
- 验证: 全流程孤检绿(10项:篝火cancel/confirm/大门无断点cancel/confirm/次钮隐藏/三钮形态/次确认开局/主确认续玩no-op——临时件不入库,T5探针承载正式验收);S9/S10/S13归基线
2026-11-01 18:11:42 +08:00
DKChan 98470adc4d 卡④T3(迭代47·营地场景图批): 四柜台拆解四子界面独立可开+CampRefreshBus单口+CampOverlay退役——五句柄落子容器/场景点选改挂/生涯改挂笔记本/关层收闭
- 四view新件(自CampOverlay承载面迁移,界面逻辑复用零重设计契约4): forge_view(钥匙门+两区六路线行板)/treasure_view(12件行板+同调装配段+Y升级行+钥匙门)/tree_view(脉分组行板+Fork双态)/codex_view(占位挂起壳,锁文随形态订正『柜台』称谓L2)——各带返回钮发closed(关层归presenter);公共壳CampSubViewBase(实底dim+铭牌+钥石徽+返回钮)
- CampRefreshBus新件: 各写口尾→现开界面刷新+场景根HUD一闸(卡⑤HUD同口挂接;非autoload非信号广播——组合根单点推送);presenter八写口尾refresh_camp→bus.refresh(F9:七口+on_camp_buy双尾)
- 承载形态(§9-2拍板): 单一内页切换=场景根留底——模态栈open只隐上方登记层(modal_stack.gd:27实测),四子界面登记于场景根之上且实底遮挡,双开互斥靠遮挡天然成立;V2断言按留底派生(T5落)
- ui_registry: LAYER_CONTRACT camp_overlay槽→camp_scene(shell camp_screen)+forge/treasure/tree/codex_view四槽;node()改经modals.camp.X二层(卡⑥契约2);ui_camp_modals五枚成员(overlay枚随退役删除——卡⑥契约4)
- camp_screen: 持camp_scene取代CampOverlay成员;main_ui_build: 场景装配(布局表ConfigDB注入)+四view实例化+信号接线(意图→presenter/closed bind携视图/object_pressed→组合根路由)
- presenter承接面: 五目标改线(ctx.views束——成员预算12≤15)+open_camp_forge/treasure/tree/codex四开层口+on_camp_sub_closed+on_camp_closed关层收闭(营地层与选档层同收+开始层恢复);main_overlay_flow._on_camp_object_pressed五物件路由(gate/campfire归T4)+生涯营地语境关层分支(笔记本改挂——CareerOverlay本体零改);main.gd薄委托零新增成员
- 退役: camp_overlay_view.gd删除(吸收即死);旧形态断言同批摘除(test-conventions §5.5):theme_projection等高源码断言+最近冒险行投影段(卡面削弱申报授权:注销,条带Q4=甲撤下)
- 同批迁移: forge_workbench_test承载类camp_overlay_view→forge_view+_forge._X扁平化(断言零改12项绿);system_test三处机械随迁(V2实例化/:108同一性链/:121候选清单——camp_scene句柄更名)
- 验证: 冒烟boot PASS(15项0失败——装配全链通)+全流程孤检绿(12项:五句柄/铺层/点选路由四子界面/返回/生涯改挂/营地语境分支/关层收闭——临时件不入库);S9/S10/S13归基线(codex头注日期清偿/_build_shell超局部变量按顶栏底栏拆)
2026-11-01 18:06:45 +08:00
DKChan 4b5e3f27e8 卡④T2(迭代47·营地场景图批): CampSceneView场景视图+camp_scene_layout.tres布局表+V14单测——场景视图/布局坐标/V14三面落地
- schema两件: CampSceneLayerSpec(层键+贴图矩形+命中矩形+命中键)+CampSceneLayoutTable(canvas/hud_band_h/gate_gap/wall_band/layers+validate()按面拆四查:身份贴图/命中边界/命中互斥/围墙带包络)
- 数据表camp_scene_layout.tres: 10层坐标唯一事实源(布局草案值+§5已裁:布告板(845,268,130,140)/门扇(520,250,240,220)门洞对齐/帐篷300x170;贴图矩形=contain终稿实测尺寸锚定:底中心/顶锚/底锚按物件语义,campfire命中(560,640,160,80)只取下半)
- ConfigDB接线: 表注册+表现域容器camp_scene_layout字段+装载期validate()逐问题_fatal(fail-fast红线:命中区越画面/相交/侵顶栏=数据致命)
- CampSceneView: setup_layers/bind_hotspots/get_hotspot_rect/set_feedback_state/set_hud_band五公开口+object_pressed信号; Control根+TextureRect层子节点序=z序(禁CanvasLayer/z_index/Sprite2D); 根单点_gui_input命中判定(层全IGNORE); 四态反馈:悬停=Candle轮廓+名牌(FS_HINT/INK_ON_DEEP/BG_ROOT板,画面内钳制)+Tween逐级整像素浮动/按下=位移1px/禁用=ShaderMaterial去饱和+降明度(零新素材,modulate只有明度轴); 素材缺位程序化占位冒烟安全; 命中键=七物件绑定(gate/forge/dummy/chest/notebook/board/campfire)
- V14单测camp_scene_layout_test.gd(11断言): 校验函数通过/七键集/两两不相交/不越画面不侵顶栏/篝火只下半/门扇对齐缺口/布告板挂墙带/10层序/ConfigDB装载链——全表关系谓词零坐标字面量; run_all manifest登记expect=0
- 验证: --editor --quit刷类缓存后V14孤跑绿(11项)+视图孤检绿(9项:子节点序/合成点击信号/悬停名牌/拖出取消/禁用不发/HUD槽——临时件不入库,T5探针承载正式验收); S9/S13归基线(gdstyle新违规4条已按职责轴拆函数清偿:validate按面四拆/_gui_input三拆/setup_layers构建段三拆/循环内分配提_build_layer_node)
2026-11-01 17:27:42 +08:00
DKChan aa4aa1dd20 卡④T1进度(迭代47·营地场景图批): UiKit.TEX_CAMP_*十键常量落位+九张终稿处理达标(worktree物理落盘)——围墙层待美术线重出(用户裁读法A)
- 用户裁留痕(2026-11-01): 围墙层终稿与锁定坐标口径结构性冲突(桩身实测≈295px vs 锁定43px,裁带(0,240,1280,70)不可执行)三读法择A=重出;停机呈报已吸收即删
- 九张后处理(授权=/iter开工令§0.3-4)达标并落盘worktree game/assets/art/scene/(素材面整目录不入git,2026-10-02裁;合并落地时物理拷贝主区): 去底(边界泛洪+奶油描边屏障)/内容裁剪(水印随裁去除)/等比contain缩放(非整数缩放L2登记:board130x121/gate240x211/dummy120x180/forge240x193/tent251x170/chest106x90/notebook100x66/campfire160x149)/bg÷2最近邻1280x800
- 营地主色板32色收敛(§4.1场景环境子板两道门合规:并集272→32,三通道差全<8对数=0);四联底测试条目检过——在途artwork/2026-11-01-营地终稿后处理/
- 十键先定常量防断链(load_tex exists守卫缺位null回退冒烟安全);布局矩形唯一事实源=camp_scene_layout.tres随T2落盘;围墙一张待重出后补齐T1,新稿落artwork/candidates/scene_camp_wall/即续跑
2026-11-01 16:35:40 +08:00
DKChan 3a9c6ff453 卡⑥T1(迭代47·营地模态句柄子容器批): UiCampModals新件+camp_overlay句柄迁移modals.camp.overlay二层——零行为变化
- 新建game/presentation/ui_camp_modals.gd(.uid同笔): RefCounted子容器,现役一枚overlay,头注自陈终态六枚/新营地句柄禁平铺回ui_modals(契约2/4/5)
- ui_modals.gd: 持camp成员(_init与flow同拍构造)+删平铺camp_overlay——类变量14→14,头注同步(十二平铺句柄+营地域子容器)
- main_ui_build.gd装配10处+ui_registry.gd node()分支返回属性链(key平铺不改名,camp.*解析分支随卡④⑤)+改指面探针五件/system_test共43处改指ui.modals.camp.overlay(断言零改判据)
- 营地段断言消息文本追加尾缀〔句柄子容器批 V1〕×33+〔句柄子容器批 V2〕×1(V1c合同树序链——独立验收审计尾缀grep义务落点)
- 验证: 类缓存刷新(--editor --quit,UiCampModals入册)后改指面8文件逐件复跑绿——system_test 69项0失败+ui_struct/screen_flow/ui_probe PASS+click问题=0+shot camp出图问题=0; S13 gate绿(143件零NEW)+ui_modals '^var '计数=14留痕; 全仓modals.camp_overlay平铺寻址零残留
2026-11-01 08:00:04 +08:00
DKChan 5ad72f44a0 合并(卡⑥v1.1·双审后执行级订正): 类缓存刷新步/static_scan路径/V尾缀落点/V3计数留痕/契约计数与退役句/迁移面实测8文件/node()改写措辞——grilling+godot-master双审通过,设计面零改动;合回后post-merge钩子自动跑CI全量域闸 2026-11-01 06:55:25 +08:00
DKChan c585a6bd73 修卡(卡⑥v1.0→v1.1·grilling+godot-master双审后执行级订正): T0命令路径订正game/tests/static_scan.sh+禁手改基线; T1补类名缓存刷新步(--editor --quit,语言纪律5)+ui_modals头注同步+断言尾缀落点步+复跑面扩改指面8文件+句柄同一性链:108; V3判据改gate绿+计数=14留痕+观测面局限申报(闸对14/15同零命中); 契约2计数句(过渡期含overlay/终态六枚)+契约3注S13兜+2起+契约4补overlay随卡④T3退役删除; F4迁移面实测8文件40余处入卡; node()措辞改写分支key不改名+白名单收窄match平铺; T2切分预案——设计面零改动,双审确认与现役先例同构 2026-11-01 06:55:16 +08:00
DKChan c009cf7b1e 合并(迭代47写卡批·机检预算裁决): 卡⑥营地模态句柄子容器批v1.0新立排执行序第一+卡④v3.2/卡⑤v1.5子容器口径+待办清单分域重构条登记+roadmap换代——停机呈报裁决最小改动落地,零基线欠账;合回后post-merge钩子自动跑CI全量域闸 2026-11-01 06:16:13 +08:00
DKChan b991f52ac2 写卡+编排(迭代47机检预算裁决·用户令): 新立卡⑥营地模态句柄子容器批v1.0(UiCampModals分域容器+camp_overlay句柄迁移+node()camp二层寻址,迭代47执行序第一位,≈0.75d)——停机呈报裁决最小改动:营地六句柄落子容器禁平铺; 卡④v3.1→v3.2(文件表ui_camp_modals行+落位预检/预算三查补行+存量触面补列ui_modals/ui_registry/main_ui_build三件+S13预期14→14+T0/T3子容器口径); 卡⑤v1.4→v1.5(浮窗句柄落子容器+契约7寻址钉死+「14→15恰贴」算术缺陷作废订正); 待办清单(营地条实况换代+开工门销号+执行序)+新登记「模态句柄册分域重构」待写卡条(全量分家另立批); roadmap迭代47行卡⑥排第一+周期≈15; 复核报告裁决注记——pipeline_gate绿 2026-11-01 06:16:06 +08:00
DKChan cd121b18c2 合并(迭代47文档批追绿): S11禁用词「单元套件」→「单元测试」README:142+卡④V14两处——红项修复,合回post-merge复验追绿 2026-11-01 05:04:05 +08:00
DKChan 83a4fbf5c5 修复(迭代47文档批追绿·fix-i47-term): S11禁用词「单元套件」→正词「单元测试」两处(README:142能力行真实入口+卡④V14真实入口格)——2026-10-22 测试术语重构批禁用词表在案,CI红项修复,合回post-merge复验追绿 2026-11-01 05:03:52 +08:00
DKChan 70d071f850 合并(迭代47文档批·开工门落文): camp-scene-prompts v1.3 §5五裁点改已裁+art-bible v3.31 §6.2同步+卡面指针随动与义务了结+V14测试名钉死+README V14锚——用户令非迭代义务本会话清零;合回后post-merge钩子自动跑CI全量域闸 2026-11-01 04:55:38 +08:00
DKChan 8be5d425ca 文档批(迭代47开工门落文·用户令本会话完成): camp-scene-prompts v1.2→v1.3 §5待裁五点改已裁(帐篷300×170追认/门扇240×220追认+门洞x520-760对齐/布告板(845,268,130,140)/描边奶油色追认/环境子板色值不前置T1量化后按§4.1登记); art-bible v3.30→v3.31 §6.2待裁行同步改已裁; 卡④v3.1卡⑤v1.4版本指针v3.24→v3.31+§5反哺点义务了结(落文完成·README已入表确认)+V14测试名钉死camp_scene_layout_test.gd:_test_layout_hotspot_geometry; README能力表V14用例锚补入; 复核报告遗留节换代——非迭代义务本会话清零,迭代47仅剩/iter发令 2026-11-01 04:55:28 +08:00
DKChan 4554e0e702 合并(迭代47写卡批·双审修订): 卡④v3.1+卡⑤v1.4——双审后用户令修订,开工门四项裁断销号(五裁点拍板/P3·P5口径/T1后处理随iter令)+Esc四态语义契约9新立+浮窗自收+判据订正+成片漂移就地订正;零待裁点零阻塞,迭代47可开工;合回后post-merge钩子自动跑CI全量域闸 2026-11-01 04:41:29 +08:00
DKChan b75688c6ca 修卡(迭代47双卡·grilling+godot-master双审修订): 卡④v3.1——开工门四项裁断销号(五裁点逐点拍板/P3·P5销号口径/T1后处理随iter令授权)+现状事实换2026-11-01盘面(smoke退役宿主改指·句柄迁移·行号实测·TEX_CAMP常量落点)+Godot风险项落卡(1024档换算式逐张登记·节点选型钉死Control根+TextureRect+子节点序·layout fail-fast校验入V14·V1场景层序断言·T2实现约束); 卡⑤v1.4——契约9 Esc四态语义与接线定稿(事件驱动_unhandled_input·禁Input轮询·不挂ui_cancel·pressed非echo)+契约3谓词钉死+契约4动线首步浮窗自收+契约5触发主体+V4/V6判据订正+死信排序约束改述+文件表补ui_modals/main_ui_build+V7扫描面扩两新件; 双审复核报告落reports——零待裁点零阻塞,迭代47可开工 2026-11-01 04:41:21 +08:00
DKChan ab5a45af00 合并(迭代46卡C·棋盘格边长口径收口批v2.0): 格边长算式单点化+探针人造阈值退役(MIN_CELL删·等价守卫·推定留痕)+四档板型入卫(62→74checks)——行为零变化·独立验收审计通过·批末代码域闸七闸all_green·锚点读数留档(基座40/33/30/28·全屏46/46/46/46)与卡§2.7逐值一致;battle.md:144下限句退役走反哺点通道;拆批续合回=迭代46最后一卡,合回后post-merge钩子自动跑CI全量域闸 2026-11-01 03:29:48 +08:00
DKChan afee92476d 合并(alpha清单状态刷新批·用户令): 08-Alpha版本标准三行现状换代——营地交互重做行(卡④美术闭合v3.0重立+同编迭代47)/真人试玩轮次行(未核销面换代㉚–㊸)/战斗一屏样张行(待做→部分在役,规格半闭合)——v1.20→v1.21+02索引指针随动;条目零增删;合回落地post-merge钩子自动跑CI全量域闸 2026-11-01 03:13:10 +08:00
DKChan b5433285b4 文档(alpha清单状态刷新·用户令): 08-Alpha版本标准三行现状换代——①营地交互重做行换代(卡④美术设计闭合=样张过审2026-10-31+v3.0现行有效卡重立,与卡⑤Esc浮窗同编迭代47待开工;删'余三前置未销号/卡⑤未编排'过期句)②真人试玩轮次行换代(未核销面=㉚–㊸待用户核销,旧句'㉚㉛未核销+迭代34三项待登记'过期——三项已登记㉜,迭代42无玩家可见改动未登记)③战斗一屏样张行待做→部分在役(规格半闭合=目标规格清单落art-bible五张规格表2026-10-30+五项实装定值与样张机检随迭代43入库;未结半=出口未达,方向稿用户目检㊶先行)——v1.20→v1.21,02系统清单版本指针随动;条目零增删,Alpha内容范围冻结不动 2026-11-01 03:13:03 +08:00
DKChan 546ed1d611 iter46-收尾 2026-11-01 03:10:26 +08:00
DKChan 37ed6ab426 iter46-卡1:棋盘格边长口径收口批v2.0——MIN_CELL阈值退役改等价守卫+算式单点_cell_for+四档入卫(74checks/探针两档绿/审计过) 2026-11-01 03:10:25 +08:00
72 changed files with 2660 additions and 1008 deletions

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.30,历次修订明细见 git 提交史
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.31,历次修订明细见 git 提交史
>
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -395,7 +395,7 @@
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。
- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。
- **事实源边界**:解锁条件/价格/存档写入归元进度 GDD 与 `MetaProgression`;素材命名与路径归 `asset-path-map`;清单登记归 `art-assets-checklist` §3.5。
- **待裁(本节不填数,由作者定)**:① **营地环境子板色值**——色彩脚本只给定色方向,落地色值属 §4.1 单点,须过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门;② **门扇与门洞的高宽比例**——中景带自底部往上约 2/3 处起之后高度有限,门扇高度远超围墙带,两者关系待作者重定;③ **帐篷的落地尺寸**——帐篷体量大于被替换的旧承载件,尺寸待作者重定;坐标草案的现行出口见 `camp.md` 的指路句。
- **已裁(2026-11-01 用户裁,随迭代 47 卡④ 开工门销号)**:① **营地环境子板色值**——不前置拍板:T1 量化场景实际用色后按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——门扇 240×220 追认现值,坐标按门洞 x520–760 对齐(`camp-scene-prompts.md` §2.3);③ **帐篷的落地尺寸**——300×170 追认现值(各素材第一张样本稿已过审为终稿,重定即触发重出,不裁)。
### 6.3 三关图类型色板候选(board_type 换挡方向)
+7 -7
View File
@@ -1,4 +1,4 @@
# 营地场景 · 分层生图提示词卡 v1.2
# 营地场景 · 分层生图提示词卡 v1.3
> **性质**:营地场景图(1280×800,一张可点击场景图 + 十张分层:背景层 1 + 围墙层 1 + 八个物件层)的出图执行侧文件。**场景级资产,与角色级 [art-prompts](art-prompts.md) 并列,两文件互不覆盖**。
> **依据**:色彩与氛围 = `reports/提案-营地色彩与氛围脚本-2026-10-23.md`(本卡不复述色值);坐标与层序 = `artwork/2026-10-23-营地场景/布局草案-1280x800.html`;物件命名与消费链 = [asset-path-map](asset-path-map.md);画法与交付形态 = [art-bible](art-bible.md) §6.2。冲突时色彩以色彩脚本为准,画法以 art-bible 为准。
@@ -171,10 +171,10 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
---
## 5. 待裁(不阻塞出图,阻塞坐标表与定稿)
## 5. 已裁(2026-11-01 用户裁,随迭代 47 卡④ 开工门销号;不再阻塞坐标表与定稿)
1. **帐篷尺寸**:现 300×170 待重定,重定后按比例重出
2. **门扇高宽**:现 240×220,围墙带只有 43px 高;围墙独立成层后门扇叠在门洞上,两者高宽关系待裁
3. **布告板位置**:现 y330 悬空在围墙下方,建议 `(845, 268, 130, 140)` 挂回围墙带
4. **描边颜色追认**:本卡按奶油色(去底技术前提);若改参照物的深灰褐,需换去底方案
5. **营地环境子板色值**:art-bible §4.1 为数值单点,色值待作者拍板,本卡不复述
1. **帐篷尺寸**:300×170 追认现值(各素材第一张样本稿已过审为终稿;重定即按比例重出、不裁)
2. **门扇高宽**:240×220 追认现值,坐标按门洞 x520–760 对齐(§2.3);围墙带桩身高 43px、门扇叠在门洞上的叠放关系随此锁定
3. **布告板位置**:采纳建议值 `(845, 268, 130, 140)` 挂回围墙带(y330 悬空案弃);终值随坐标表(卡④ `camp_scene_layout.tres`)落盘
4. **描边颜色**:奶油色追认——去底脚本「奶油描边膨胀 3px 当屏障」的技术前提随现值锁定(§1 注记同源);改深灰褐须换去底方案全批重出,不裁
5. **营地环境子板色值**:不前置拍板——T1 量化场景实际用色后按 art-bible §4.1 两道门登记(语义检查+RGB 三通道差 ≥8/255),本卡仍不复述色值
+1 -1
View File
@@ -15,7 +15,7 @@
| `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) |
| `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 |
| `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 |
| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.20**,2026-10-13) |
| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.21**,2026-10-13) |
## systems/ 五个循环
+4 -4
View File
@@ -2,7 +2,7 @@
> **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。
> **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。
> **状态**:**已定稿(v1.20)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **状态**:**已定稿(v1.21)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **成文**:2026-10-13。
## 本文怎么用
@@ -104,7 +104,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 开始覆盖层 | —— | 在役 |
| 选档界面(3 存档位) | —— | 在役(迭代 33 卡③——空槽新档/有档进档/删档二次确认+摘要三项;2026-10-22 用户裁增行〔反哺 #8〕) |
| 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](ux/camp.md) | 在役(场景图与七物件点选随营地交互重做列入 Alpha,见下行) |
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md)+[需求澄清](../../reports/需求澄清-营地交互重做-2026-10-18.md) | 部分在役(卡①②③ 已随迭代 33 实装合回,2026-10-22;卡④营地场景图 2026-10-21 用户令退回美术设计段,美术生成方案提案 2026-10-22 成文,余三个用户侧前置未销号:像素规格执行(Q6 甲过渡口径已裁)、点选反馈设计稿、参照物;卡⑤ Esc 浮窗随卡④ 代码波同行、未编排。2026-10-21 用户裁:五卡整体列入 Alpha。出口=五卡各自验收标准通过+营地屏用户目检通过) |
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md)+[需求澄清](../../reports/需求澄清-营地交互重做-2026-10-18.md) | 部分在役(卡①②③ 已随迭代 33 实装合回,2026-10-22;卡④营地场景图美术设计已闭合(样张过审 2026-10-31)、v3.0 现行有效卡重立,与卡⑤ Esc 浮窗同编迭代 47(待开工)。2026-10-21 用户裁:五卡整体列入 Alpha。出口=五卡各自验收标准通过+营地屏用户目检通过) |
| 生涯页 | [生涯](systems/跨局循环/生涯.md) | 在役(期一) |
| 关卡地图屏 | [level-map](ux/level-map.md) | 在役 |
| 战斗屏 | [battle](ux/battle.md) | 在役 |
@@ -135,7 +135,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 机器可读的呈现断言(对比度 / 结构) | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 分辨率两档(1280×800 / 1920×1080) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役 |
| 绘制调用数达标 | —— | 在役 |
| 真人试玩轮次 | [体验验证清单](../../体验验证清单.md) | 在役(义务维持;当前轮次未清:㉚/㉛ 未核销,迭代 34 三条目检项待登记) |
| 真人试玩轮次 | [体验验证清单](../../体验验证清单.md) | 在役(义务维持;当前轮次未清=㉚–㊸ 各条待用户核销——迭代 32 起各批实机目检项登记不阻塞,迭代 42 无玩家可见改动未登记) |
---
@@ -157,7 +157,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 站桩帧动画 | [待办清单](../../待办清单.md) | 待做(列入 Alpha 定版前置清单:定版前完成最小版,路线预选拆件驱动;未完成则顺延 Beta;不占功能迭代) |
| 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) |
| UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(2026-10-24 用户裁现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) |
| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 待做(Alpha 硬前置:站桩帧动画的定版前置与主角+三 Boss 重产都依赖它;出口=用户目检通过的战斗屏样张,并产出尺寸档/色板/明度档清单) |
| 战斗一屏样张(界面美术的终稿闸) | [美术圣经](../art/art-bible.md) §1.4 | 部分在役(规格半已闭合=目标规格清单已落 art-bible 五张规格表〔2026-10-30 合回〕,五项实装定值与样张机检随迭代 43 入库;未结半=出口未达——方向稿用户目检(体验验证清单㊶)先行,样张整体过闸随其后。站桩帧动画的定版前置与主角+三 Boss 重产依赖照旧) |
| UI 氛围层与九宫格切片观感修 | [待办清单](../../待办清单.md) | 部分在役(光柱硬边已修=连续 alpha 渐变;未结半=九宫格切片错位——Alpha 内做最小修复:核对素材尺寸,必要时重出九宫格素材,全套素材重出排 Beta;出口=结算屏与图例屏错位消失,渲染闸+用户目检通过) |
| UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:①滑轨填充随迭代 25 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) |
+5 -1
View File
@@ -30,12 +30,13 @@ const TABLE_MANIFEST := {
"feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
"forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面
"battle_layout": "battle_layout.tres", # BattleLayoutTable——战斗屏分区几何与明细带高度单行
"camp_scene_layout": "camp_scene_layout.tres", # CampSceneLayoutTable——营地场景 10 层贴图/命中矩形与门洞缺口单行
}
var combat: ConfigDomainCombat # 战斗编排域七表(board_gen/variation/status_effects/enemy/skill/hero/settlement)
var run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
var presentation: ConfigDomainPresentation # 表现层布局域一表(battle_layout)
var presentation: ConfigDomainPresentation # 表现层布局域两表(battle_layout/camp_scene_layout)
var is_ready := false
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
@@ -149,6 +150,9 @@ func _assign_loaded(loaded: Dictionary) -> void:
crossrun.feats = loaded.get("feats")
crossrun.forge = loaded.get("forge")
presentation.battle_layout = loaded.get("battle_layout")
presentation.camp_scene_layout = loaded.get("camp_scene_layout")
for problem: String in presentation.camp_scene_layout.validate():
_fatal("camp_scene_layout", problem) # 布局几何红线(命中区越画面/相交/侵顶栏)=数据致命
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
combat.board_gen._resolver = _resolver
combat.enemy._resolver = _resolver
+2 -1
View File
@@ -1,6 +1,7 @@
## 表现层布局域容器(D2 域容器——config_db 表持有按域收敛):战斗屏布局一表类型化持有。
## 表现层布局域容器(D2 域容器——config_db 表持有按域收敛):战斗屏与营地场景布局两表类型化持有。
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainPresentation
extends Resource
var battle_layout: BattleLayoutTable
var camp_scene_layout: CampSceneLayoutTable
@@ -0,0 +1,13 @@
## 营地场景单层规格(camp_scene_layout.tres 行件):层键+贴图矩形+命中矩形+命中键。
## z 序=表内数组序(视图子节点序同源派生);坐标一律终稿画布 1280×800 像素坐标。
class_name CampSceneLayerSpec
extends Resource
@export var key: StringName = &"" # 层键(对齐 UiKit.TEX_CAMP_* 常量后缀)
@export var texture_rect: Rect2 = Rect2() # 贴图矩形(1:1 像素贴图落位)
@export var hotspot: Rect2 = Rect2() # 命中矩形(零矩形=不可点层——bg/wall/tent)
@export var hotspot_key: StringName = &"" # object_pressed 载荷键(可点层必填)
func is_clickable() -> bool:
return hotspot.size.x > 0.0 and hotspot.size.y > 0.0
@@ -0,0 +1 @@
uid://bll8d0s1nmarf
@@ -0,0 +1,67 @@
## 营地场景布局表 schema(camp_scene_layout.tres):10 层贴图矩形/命中矩形/门洞缺口/围墙带包络/顶栏禁区。
## 坐标数值唯一事实源=数据行本表——视图、探针、单测一律读表取值,禁另写坐标字面量。
class_name CampSceneLayoutTable
extends Resource
@export var canvas := Vector2(1280, 800) # 终稿画布(工程档同幅面)
@export var hud_band_h := 48 # 顶栏禁区高度(命中不侵)
@export var gate_gap := Vector2i(520, 760) # 门洞缺口 x 范围(透出背景层小路,禁填死)
@export var wall_band := Rect2(0, 240, 1280, 70) # 围墙带包络(门柱顶 240—桩底 310;围墙贴图须落在其内)
@export var layers: Array[CampSceneLayerSpec] = []
## 加载期 fail-fast 校验(ConfigDB 装载即查,问题非空=数据致命)。
## 校验面按件拆分:层身份与贴图矩形/命中区边界/命中区互斥/围墙带包络。
func validate() -> Array[String]:
var problems: Array[String] = []
var clickables: Array[CampSceneLayerSpec] = _collect_clickables(problems)
_check_hotspot_bounds(clickables, problems)
_check_hotspot_disjoint(clickables, problems)
_check_wall_band(problems)
return problems
func _collect_clickables(problems: Array[String]) -> Array[CampSceneLayerSpec]:
var seen: Dictionary[StringName, bool] = {}
var clickables: Array[CampSceneLayerSpec] = []
for layer in layers:
if layer.key == &"":
problems.append("层键为空")
continue
if seen.has(layer.key):
problems.append("层键重复: %s" % layer.key)
seen[layer.key] = true
if _out_of_canvas(layer.texture_rect):
problems.append("贴图矩形越画面: %s %s" % [layer.key, layer.texture_rect])
if layer.is_clickable():
if layer.hotspot_key == &"":
problems.append("可点层缺命中键: %s" % layer.key)
clickables.append(layer)
elif layer.hotspot_key != &"":
problems.append("不可点层带命中键: %s" % layer.key)
return clickables
func _check_hotspot_bounds(clickables: Array[CampSceneLayerSpec], problems: Array[String]) -> void:
for h in clickables:
if h.hotspot.position.x < 0.0 or h.hotspot.position.y < hud_band_h \
or _out_of_canvas(h.hotspot):
problems.append("命中区越画面或侵顶栏禁区: %s %s" % [h.key, h.hotspot])
func _check_hotspot_disjoint(clickables: Array[CampSceneLayerSpec], problems: Array[String]) -> void:
for i in clickables.size():
for j in range(i + 1, clickables.size()):
if clickables[i].hotspot.intersects(clickables[j].hotspot):
problems.append("命中区相交: %s × %s" % [clickables[i].key, clickables[j].key])
func _check_wall_band(problems: Array[String]) -> void:
for layer in layers:
if layer.key == &"wall" and not wall_band.encloses(layer.texture_rect):
problems.append("围墙贴图越围墙带包络: %s" % layer.texture_rect)
func _out_of_canvas(rect: Rect2) -> bool:
return rect.position.x < 0.0 or rect.position.y < 0.0 \
or rect.end.x > canvas.x or rect.end.y > canvas.y
@@ -0,0 +1 @@
uid://c6p8wyvcc5upp
+82
View File
@@ -0,0 +1,82 @@
[gd_resource type="Resource" script_class="CampSceneLayoutTable" load_steps=13 format=3]
[ext_resource type="Script" path="res://data/schemas/camp_scene_layout_table.gd" id="1"]
[ext_resource type="Script" path="res://data/schemas/camp_scene_layer_spec.gd" id="2"]
[sub_resource type="Resource" id="1"]
script = ExtResource("2")
key = &"bg"
texture_rect = Rect2(0, 0, 1280, 800)
hotspot = Rect2(0, 0, 0, 0)
hotspot_key = &""
[sub_resource type="Resource" id="2"]
script = ExtResource("2")
key = &"wall"
texture_rect = Rect2(0, 261, 1280, 49)
hotspot = Rect2(0, 0, 0, 0)
hotspot_key = &""
[sub_resource type="Resource" id="3"]
script = ExtResource("2")
key = &"board"
texture_rect = Rect2(845, 268, 130, 121)
hotspot = Rect2(845, 268, 130, 140)
hotspot_key = &"board"
[sub_resource type="Resource" id="4"]
script = ExtResource("2")
key = &"gate"
texture_rect = Rect2(520, 250, 240, 211)
hotspot = Rect2(520, 250, 240, 220)
hotspot_key = &"gate"
[sub_resource type="Resource" id="5"]
script = ExtResource("2")
key = &"dummy"
texture_rect = Rect2(340, 520, 120, 180)
hotspot = Rect2(340, 520, 120, 180)
hotspot_key = &"dummy"
[sub_resource type="Resource" id="6"]
script = ExtResource("2")
key = &"forge"
texture_rect = Rect2(40, 467, 240, 193)
hotspot = Rect2(40, 420, 240, 240)
hotspot_key = &"forge"
[sub_resource type="Resource" id="7"]
script = ExtResource("2")
key = &"tent"
texture_rect = Rect2(982, 520, 251, 170)
hotspot = Rect2(0, 0, 0, 0)
hotspot_key = &""
[sub_resource type="Resource" id="8"]
script = ExtResource("2")
key = &"chest"
texture_rect = Rect2(1097, 700, 106, 90)
hotspot = Rect2(1085, 700, 130, 90)
hotspot_key = &"chest"
[sub_resource type="Resource" id="9"]
script = ExtResource("2")
key = &"notebook"
texture_rect = Rect2(818, 704, 100, 66)
hotspot = Rect2(818, 690, 100, 80)
hotspot_key = &"notebook"
[sub_resource type="Resource" id="10"]
script = ExtResource("2")
key = &"campfire"
texture_rect = Rect2(560, 571, 160, 149)
hotspot = Rect2(560, 640, 160, 80)
hotspot_key = &"campfire"
[resource]
script = ExtResource("1")
canvas = Vector2(1280, 800)
hud_band_h = 48
gate_gap = Vector2i(520, 760)
wall_band = Rect2(0, 240, 1280, 70)
layers = [SubResource("1"), SubResource("2"), SubResource("3"), SubResource("4"), SubResource("5"), SubResource("6"), SubResource("7"), SubResource("8"), SubResource("9"), SubResource("10")]
+1 -1
View File
@@ -7,7 +7,7 @@ extends RefCounted
## 三束按域收拢(u21——同 u20 形;views=视图句柄/getters=查询 Callable 活引用/flow=编排 Callable):
## 原 12 参长签名收 4 参——字段即原构造参数逐一同名迁移,键面=消费方改址合同。
var db
var views: Dictionary = {} # {camp_overlay, shop_overlay, toast, start_overlay, logger} # type-hint: ignore(三束异构混载——视图句柄/查询 Callable/编排 Callable)
var views: Dictionary = {} # {camp_scene, forge_view, treasure_view, tree_view, codex_view, shop_overlay, toast, start_overlay, save_select_overlay, logger, refresh_bus} # type-hint: ignore(三束异构混载——视图句柄/刷新单口/查询 Callable/编排 Callable;卡④ T3 营地域改挂)
var getters: Dictionary = {} # {meta_get, save_get, runner_get} # type-hint: ignore(同上——Callable 束)
var flow: Dictionary = {} # {open_modal, close_modal, refresh_presentations} # type-hint: ignore(同上——Callable 束)
+48 -15
View File
@@ -7,7 +7,7 @@ extends RefCounted
var db
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面)
var _save_get: Callable # () → 存档承载现值(同上——营地 recent 摘要读数面)
var camp_overlay: CampOverlay
var views: Dictionary = {} # {camp_scene, forge_view, treasure_view, tree_view, codex_view, shop_overlay, toast, start_overlay, save_select_overlay, logger, refresh_bus} # type-hint: ignore(三束异构混载——视图句柄/单口件;营地域句柄经 ctx.views 取用——组合根 ui.modals.camp 子容器同源)
var shop_overlay: ShopOverlay
var toast: ToastStack
var start_overlay: Control # 开始层互斥面(开合仿设置面先例——非模态且树序在后,规范 2.3)
@@ -23,7 +23,7 @@ func _init(ctx: CampFlowContext) -> void:
db = ctx.db
_meta_get = ctx.getters.meta_get
_save_get = ctx.getters.save_get
camp_overlay = ctx.views.camp_overlay
views = ctx.views # 营地域五目标+刷新单口+关层收闭面(卡④ T3——CampOverlay 退役后投影目标改线)
shop_overlay = ctx.views.shop_overlay
toast = ctx.views.toast
start_overlay = ctx.views.start_overlay
@@ -34,21 +34,54 @@ func _init(ctx: CampFlowContext) -> void:
_refresh_presentations = ctx.flow.refresh_presentations
# 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后,
# 规范 2.3 遮挡纪律;打开时先刷条件解锁——达标未解锁就地入账)
# 打开营地场景根(进档落点——卡④ T3 改挂:开层=交互式场景图,四子界面经场景点选进)
func open_camp() -> void:
var meta = _meta_get.call()
if meta == null:
return
meta.refresh_treasure_unlocks() # 条件解锁刷新
start_overlay.visible = false
_open_modal.call(camp_overlay) # 模态栈单口(登记层开合一律走栈)
camp_overlay.open_camp(camp_data())
_open_modal.call(views.camp_scene) # 模态栈单口(场景根开——选档层被栈遮挡挂起)
views.refresh_bus.refresh(camp_data()) # 进档动线尾显式刷新一闸(卡⑤ 契约 5 时机②——HUD 读数重读)
_refresh_presentations.call()
# ---- 营地四子界面开层(场景点选路由分派——组合根 _on_camp_object_pressed;数据投喂=快照投影)----
func open_camp_forge() -> void:
_open_modal.call(views.forge_view)
views.forge_view.open_camp(camp_data())
func open_camp_treasure() -> void:
_open_modal.call(views.treasure_view)
views.treasure_view.open_camp(camp_data())
func open_camp_tree() -> void:
_open_modal.call(views.tree_view)
views.tree_view.open_camp(camp_data())
func open_camp_codex() -> void:
_open_modal.call(views.codex_view)
views.codex_view.open_camp(camp_data())
# 子界面返回钮(视图只发 closed——关层归本口;场景根留底复现,开始层不动)
func on_camp_sub_closed(view: Control) -> void:
_close_modal.call(view)
_refresh_presentations.call()
# 关闭整个营地(关层收闭——卡④ T3:营地层与选档层同收+开始层恢复,禁只关营地留下选档回闪;
# 触发面=篝火/ESC 回主菜单动线,卡④ T4 接线)
func on_camp_closed() -> void:
_close_modal.call(camp_overlay) # 模态栈单口
for v: Control in [views.camp_scene, views.forge_view, views.treasure_view, views.tree_view, views.codex_view]:
if v.visible:
_close_modal.call(v)
_close_modal.call(views.save_select_overlay)
start_overlay.visible = true
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
@@ -79,7 +112,7 @@ func on_camp_buy(attr: String) -> void:
if not meta.buy_treasure_unlock(attr):
toast.show_toast("购买被拒(余额不足或不可购买)", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
return
# Y 刷新升级行购买(双入口之一——树上 ks_refresh 同账;
@@ -87,7 +120,7 @@ func on_camp_buy(attr: String) -> void:
if not meta.buy_attune_refresh():
toast.show_toast("购买被拒", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
@@ -99,7 +132,7 @@ func on_camp_attune_toggle(item_id: String) -> void:
if not meta.toggle_attunement(item_id):
toast.show_toast("同调位已满——先卸下再装配", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
# 天赋树购买(写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧)
@@ -111,7 +144,7 @@ func on_camp_tree_buy(node_id: String) -> void:
if not ok:
toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
@@ -123,7 +156,7 @@ func on_camp_fork_toggle(group: String, side: String) -> void:
if not meta.tree_toggle_fork(group, side):
toast.show_toast("切换被拒——未激活组请先购买", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
@@ -136,7 +169,7 @@ func on_camp_forge_buy(route_id: String) -> void:
if not meta.forge_buy_level(route_id):
toast.show_toast("锻造被拒——钥石不足或路线不可锻造", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
@@ -148,7 +181,7 @@ func on_camp_forge_unlock(route_id: String) -> void:
if not meta.forge_buy_unlock(route_id):
toast.show_toast("解锁被拒——钥石不足或已解锁", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
@@ -162,7 +195,7 @@ func on_camp_forge_select(route_id: String) -> void:
if not ok:
toast.show_toast("切换被拒——路线未解锁", "warn")
return
camp_overlay.refresh_camp(camp_data())
views.refresh_bus.refresh(camp_data())
_refresh_presentations.call()
+45 -4
View File
@@ -123,19 +123,37 @@ func _start_adventure_from_menu() -> void:
# 确认弹窗分派(卡③ 契约 3):按 _confirm_consumer 路由——"delete_slot"=卡③ 删档确认
#(确认才删、取消只关窗——裁决 3)。"new_adventure" 消费方已随卡② 主菜单确认链摘除退役
#(mn6/V5——主菜单零确认残留),分派机制保留(卡④ 到位接入第三消费方不返工)。
#(确认才删、取消只关窗——裁决 3)。"gate"/"gate_new"=卡④ 大门两分支消费方(有/无断点),
# "campfire_leave"=篝火二次确认(卡④ T4 接入——分派机制扩展第三/第四消费方)。
func _on_confirm_accepted() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
match _confirm_consumer:
"delete_slot":
save_manager.slots.delete_slot(_confirm_consumer_slot)
ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
"gate":
_camp_pres.on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭)
_resume_from_menu() # 继续上一段冒险(现役续玩编排同源——卡④ 契约 4)
"gate_new":
_camp_pres.on_camp_closed()
_start_adventure() # 开始新冒险(现役开局编排同源)
"campfire_leave":
_camp_pres.on_camp_closed() # 篝火确认离场=营地与选档层同收+开始层恢复(回主菜单)
_confirm_consumer = ""
# 次确认分派(三钮形态中钮——大门「开始新冒险」出口;卡④ T4)
func _on_confirm_secondary() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
match _confirm_consumer:
"gate":
_camp_pres.on_camp_closed()
_start_adventure()
_confirm_consumer = ""
func _on_confirm_declined() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删);关侧恢复归 ModalFlow
ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow
_confirm_consumer = ""
@@ -500,12 +518,35 @@ func _sync_status_entry() -> void:
MainOverlayFlow._sync_status_entry(self)
# ---- 探针缝保缝(click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)----
# ---- 探针缝保缝(click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd / main_overlay_flow.gd)----
func _on_camp_closed() -> void:
MainOverlayFlow._on_camp_closed(self)
func _on_camp_object_pressed(key: StringName) -> void:
MainOverlayFlow._on_camp_object_pressed(self, key) # 场景点选路由(卡④ T3——薄委托,实现归覆盖层流程承接件)
# Esc 输入入口(产品码首个键盘入口——卡⑤ T1 契约 9 接线定稿:事件驱动 _unhandled_input、
# KEY_ESCAPE 键值判定配 physical_keycode、pressed 且非 echo、禁 Input 单例轮询、不挂 ui_cancel)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and not event.echo and (event.keycode == KEY_ESCAPE or event.physical_keycode == KEY_ESCAPE):
MainEscFlow.on_esc_input(self)
func _on_esc_return() -> void:
MainEscFlow._on_return_to_menu(self)
func _on_esc_settings() -> void:
MainEscFlow._on_settings(self)
func _on_esc_quit() -> void:
MainEscFlow._on_quit(self)
func _camp_data() -> Dictionary:
return MainOverlayFlow._camp_data(self)
+50
View File
@@ -0,0 +1,50 @@
## 组合根 Esc 流程承接件(产品码首个键盘输入入口——main._unhandled_input 同名薄委托落地):
## Esc 四态路由(契约 9 判据单点=ModalStack 登记册现开集合,禁手枚名单)+三出口动线。
## 静态承接件零成员态(仿 main_overlay_flow 形态——main 侧留同名薄委托,探针 main.X 路径零改动)。
class_name MainEscFlow
extends RefCounted
## 营地语境判据(Q1 甲——场景根 visible;子界面开态抑制由契约 9① 现开集合承载,不并入本谓词)
static func is_camp_context(root: Variant) -> bool:
return root.ui.modals.camp.scene != null and root.ui.modals.camp.scene.visible
## Esc 四态优先序(契约 9——①先于②:设置开在浮窗之上态必须先命中「无反馈」,浮窗不隔层关闭):
## ①除浮窗外任一登记模态开→无反馈(子界面开态/设置开在浮窗之上)→②浮窗开→close(同键单义切换,
## Q2 甲)→③营地语境→open→④非营地→无反馈(选档/冒险/菜单语境 Esc 零响应——契约 2)
static func on_esc_input(root: Variant) -> void:
var esc: Control = root.ui.modals.camp.esc_overlay
if _other_modal_open(root, esc):
return # ① 除浮窗外任一登记模态开 → 无反馈
if esc.visible:
root.ui.modals.flow.close(esc) # ② 再按 Esc 关窗
return
if is_camp_context(root):
root.ui.modals.flow.open(esc) # ③ 营地语境唤起
# ④ 非营地语境 → 无反馈
# 登记册现开集合查询(除浮窗与营地场景根本身——场景根=语境层非抑制对象;
# 迭代登记册遍历=契约 9「判据单点」 sanctioned 读法,禁按层名手枚名单)
static func _other_modal_open(root: Variant, esc: Control) -> bool:
for m in root.ui.modals.flow.stack._list:
if m != esc and m != root.ui.modals.camp.scene and is_instance_valid(m) and m.visible:
return true
return false
## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复
static func _on_return_to_menu(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.camp.esc_overlay)
root._camp_pres.on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复)
## 设置出口(浮窗登记在设置层之下——open 只隐上方层,浮窗由树序遮挡非属性隐藏;关后重现=栈语义)
static func _on_settings(root: Variant) -> void:
root.ui.modals.flow.open(root.ui.modals.settings_overlay)
## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用)
static func _on_quit(root: Variant) -> void:
root._quit_game()
+1
View File
@@ -0,0 +1 @@
uid://caa868un7dnt4
+44 -4
View File
@@ -10,7 +10,11 @@ static func _open_settings(root: Variant) -> void:
static func _on_settings_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.settings_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
# 语境分支(卡⑤ 契约 3——谓词=camp_esc_overlay.visible,谁开的设置谁收):
# 营地浮窗路径关设置回浮窗不回主菜单(栈语义:浮窗登记在下、由树序遮挡非属性隐藏);
# 菜单路径保留开始层恢复硬副作用。
if not root.ui.modals.camp.esc_overlay.visible:
root.ui.screens.start_overlay.visible = true
# ---- 局内状态面板·只读(入口角标在两屏装配段;开合与数据喂入归组合根)----
@@ -40,7 +44,43 @@ static func _open_career(root: Variant) -> void:
static func _on_career_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.career_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
if not root.ui.modals.camp.scene.visible: # 营地语境关生涯不回开始层(笔记本入口——卡④ T3;栈已复现场景根)
root.ui.screens.start_overlay.visible = true
# ---- 营地场景点选路由(卡④ T3/T4——场景根 object_pressed 组合根分派)----
# 大门两分支(裁决 5,Q5 甲):点大门永远先出确认不直接进冒险——有断点=三出口弹窗
#(继续上一段冒险/开始新冒险/取消),无断点=是否开始新冒险(是/否);cancelled 恒=无变更退回营地。
# 篝火回主菜单必须二次确认(camp.md v2.2 裁定);确认后营地层与选档层同收+开始层恢复(关层收闭)。
static func _on_camp_object_pressed(root: Variant, key: StringName) -> void:
match key:
&"forge":
root._camp_pres.open_camp_forge()
&"chest":
root._camp_pres.open_camp_treasure()
&"dummy":
root._camp_pres.open_camp_tree()
&"board":
root._camp_pres.open_camp_codex()
&"notebook":
_open_career(root) # 生涯页入口改挂帐篷旁笔记本(CareerOverlay 本体零改——卡④ T3)
&"gate":
_open_gate_confirm(root)
&"campfire":
root._confirm_consumer = "campfire_leave"
root.ui.modals.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下")
static func _open_gate_confirm(root: Variant) -> void:
if root.save_manager.has_breakpoint():
root._confirm_consumer = "gate"
root.ui.modals.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?",
"继续上一段冒险", "开始新冒险", "取消")
return
root._confirm_consumer = "gate_new"
root.ui.modals.confirm_dialog.open("是否开始新冒险?", "是", "否")
# 生涯页快照投影 —— 承接体见 HudPresenter.career_data(开层调用点直接改引)
@@ -65,7 +105,7 @@ static func _on_save_select_closed(root: Variant) -> void:
# 空槽新档链(Q3 裁乙暂行落点=现役 CampOverlay;卡④ 重立卡后改挂场景图):卡① 创建=激活=白板落盘,
# 空槽新档链(卡④ T3 换代:落点=营地场景根——CampOverlay 随四柜台拆解退役):卡① 创建=激活=白板落盘,
# 落点开层。动线序(禁先关后开):先落点开层(开始层保持隐藏),选档层被模态栈遮挡隐藏、不关闭。
static func _on_save_select_new(root: Variant, slot: int) -> void:
root.save_manager.slots.create_slot(slot)
@@ -73,7 +113,7 @@ static func _on_save_select_new(root: Variant, slot: int) -> void:
# 有档槽进档链(契约 8 进档即激活):卡① 激活+读盘(兜底/拒读链按槽成立)→暂行落点 CampOverlay。
# 有档槽进档链(契约 8 进档即激活):卡① 激活+读盘(兜底/拒读链按槽成立)→落点=营地场景根(卡④ T3)。
static func _on_save_select_load(root: Variant, slot: int) -> void:
root.save_manager.slots.activate_slot(slot)
root._camp_pres.open_camp()
+58 -13
View File
@@ -83,7 +83,7 @@ static func _build_battle_wiring(root: Variant) -> void:
static func _build_overlays(root: Variant) -> void:
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
# < 商店 < 营地 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db)
root.ui.modals.legend_overlay = overlays.legend_overlay
@@ -92,8 +92,34 @@ static func _build_overlays(root: Variant) -> void:
root.ui.modals.shop_overlay = overlays.shop_overlay
root.ui.screens.camp_screen = CampScreenScene.instantiate()
root.add_child(root.ui.screens.camp_screen)
root.ui.modals.camp_overlay = root.ui.screens.camp_screen.camp_overlay
# 营地屏壳插位(夹商店层与设置层之间)由 LAYER_CONTRACT 营地槽统一落位——禁散落 move_child
# 营地场景根装配(布局表经 ConfigDB 单点注入——视图禁自载表路径;CampOverlay 随四柜台拆解退役)
var camp_layout: CampSceneLayoutTable = root.db.presentation.camp_scene_layout
root.ui.screens.camp_screen.camp_scene.setup_layers(camp_layout)
root.ui.screens.camp_screen.camp_scene.bind_hotspots(camp_layout)
root.ui.modals.camp.scene = root.ui.screens.camp_screen.camp_scene
# 营地四子界面(独立全屏页——场景根留底形态;句柄落 ui.modals.camp 子容器——禁平铺回 ui_modals)
var forge := ForgeView.new()
root.add_child(forge)
root.ui.modals.camp.forge = forge
var treasure := TreasureView.new()
root.add_child(treasure)
root.ui.modals.camp.treasure = treasure
var tree_view := TreeView.new()
root.add_child(tree_view)
root.ui.modals.camp.tree = tree_view
var codex := CodexView.new()
root.add_child(codex)
root.ui.modals.camp.codex = codex
# Esc 浮窗(卡⑤——登记于设置层之前的模态;三钮意图经 MainEscFlow 路由)
var esc_overlay := CampEscOverlay.new()
esc_overlay.setup_buttons()
esc_overlay.bind_signals()
root.add_child(esc_overlay)
root.ui.modals.camp.esc_overlay = esc_overlay
# 营地顶栏 HUD 栏(卡⑤——钥石余额常驻;挂卡④场景根顶栏预留位+入刷新单口目标册)
var hud_band := CampHudBand.new()
root.ui.modals.camp.scene.set_hud_band(hud_band)
# 屏壳/子界面插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child
root.ui.modals.legend_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.legend_overlay))
root.ui.modals.node_overlay.continue_pressed.connect(root._on_node_continue)
root.ui.modals.level_screen.route_chosen.connect(root._on_route_chosen)
@@ -158,9 +184,18 @@ static func _build_menu_wiring(root: Variant) -> void:
root.ui.screens.debug_badge.pressed.connect(root._on_debug_badge_pressed)
# 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入)
# 营地刷新单口(各写口尾→现开界面+场景根 HUD 一闸完成——卡④ T3;卡⑤ HUD band 随卡⑤挂册)
var camp_bus := CampRefreshBus.new()
camp_bus.bind_targets([root.ui.modals.camp.forge, root.ui.modals.camp.treasure,
root.ui.modals.camp.tree, root.ui.modals.camp.codex,
root.ui.screens.camp_screen.camp_scene.hud_band] as Array[Control])
root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db,
{"camp_overlay": root.ui.modals.camp_overlay, "shop_overlay": root.ui.modals.shop_overlay,
"toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay, "logger": root.logger},
{"camp_scene": root.ui.modals.camp.scene, "forge_view": root.ui.modals.camp.forge,
"treasure_view": root.ui.modals.camp.treasure, "tree_view": root.ui.modals.camp.tree,
"codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.shop_overlay,
"toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay,
"save_select_overlay": root.ui.modals.save_select_overlay, "logger": root.logger,
"refresh_bus": camp_bus},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"runner_get": func() -> Variant: return root.run_scope.runner},
{"open_modal": root.ui.modals.flow.open, "close_modal": root.ui.modals.flow.close,
@@ -168,14 +203,19 @@ static func _build_menu_wiring(root: Variant) -> void:
root.ui.modals.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.ui.modals.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.ui.modals.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
root.ui.modals.camp_overlay.buy_requested.connect(root._camp_pres.on_camp_buy)
root.ui.modals.camp_overlay.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.ui.modals.camp_overlay.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.ui.modals.camp_overlay.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root.ui.modals.camp_overlay.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy) # 锻造三意图(写口唯一归 MetaProgression)
root.ui.modals.camp_overlay.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.ui.modals.camp_overlay.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.ui.modals.camp_overlay.closed.connect(root._camp_pres.on_camp_closed)
# 营地信号接线(CampOverlay 退役——四子界面意图信号+场景点选组合根路由+closed 经 bind 携视图)
root.ui.modals.camp.forge.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy)
root.ui.modals.camp.forge.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.ui.modals.camp.forge.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.ui.modals.camp.treasure.buy_requested.connect(root._camp_pres.on_camp_buy)
root.ui.modals.camp.treasure.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.ui.modals.camp.tree.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.ui.modals.camp.tree.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root.ui.modals.camp.forge.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.forge))
root.ui.modals.camp.treasure.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.treasure))
root.ui.modals.camp.tree.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.tree))
root.ui.modals.camp.codex.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.codex))
root.ui.modals.camp.scene.object_pressed.connect(root._on_camp_object_pressed)
root.ui.screens.menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口
root.ui.screens.menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单
@@ -194,7 +234,12 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
# 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer,
# confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工)
root.ui.modals.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.ui.modals.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口)
root.ui.modals.confirm_dialog.cancelled.connect(root._on_confirm_declined)
# Esc 浮窗三出口接线(卡⑤——意图信号经薄委托归 MainEscFlow 动线;输入入口=main._unhandled_input)
root.ui.modals.camp.esc_overlay.return_requested.connect(root._on_esc_return)
root.ui.modals.camp.esc_overlay.settings_requested.connect(root._on_esc_settings)
root.ui.modals.camp.esc_overlay.quit_requested.connect(root._on_esc_quit)
# 调试浮窗(主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口;
# 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块;
+7 -7
View File
@@ -1,14 +1,14 @@
class_name CampScreen
extends Control
# 营地屏壳(营地覆盖层的屏壳容器)。
# 营地覆盖层视图原样构建(CampOverlay 类视图继续代码构建、零 .tscn 化改造——契约 2);
# 购买/开合接线归组合根经 CampShopPresenter(信号连接只在组合根)。
# 可独立实例化(F6/headless 挂载)——V2 探针断言载体。
# 营地屏壳(营地场景根的屏壳容器)。
# 场景图视图代码构建(CampSceneView 继续代码构建、零 .tscn 化改造);布局表经组合根注入
# (main_ui_build 装配段 setup_layers/bind_hotspots——视图禁自载表路径);购买/开合接线归组合根经
# CampShopPresenter(信号连接只在组合根)。可独立实例化(F6/headless 挂载)——V2 探针断言载体。
var camp_overlay: CampOverlay # 营地覆盖层(原 build_overlays 营地段迁入——main 组合根句柄指向本屏成员)
var camp_scene: CampSceneView # 营地场景图(交互式营地场景根——main 组合根句柄指向本屏成员)
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
camp_overlay = CampOverlay.new()
add_child(camp_overlay)
camp_scene = CampSceneView.new()
add_child(camp_scene)
+12
View File
@@ -0,0 +1,12 @@
## 营地交互域模态句柄子容器:持营地域全部模态句柄(终态六枚=营地场景根+锻造/秘宝/天赋树/图鉴四子界面+Esc 浮窗)。
## RefCounted 不挂树;随 UiModals._init 构造(与 flow 同拍)。Esc 浮窗枚随卡⑤落——禁空占位凑结构。
## 新营地句柄禁平铺回 ui_modals:营地域句柄寻址一律 ui.modals.camp.X 二层(模态句柄册分域纪律——全量分家另立批)。
class_name UiCampModals
extends RefCounted
var scene: CampSceneView # 营地场景根(交互式营地场景图——camp_screen 屏壳成员住宿)
var forge: ForgeView # 锻造工坊子界面
var treasure: TreasureView # 秘宝匣子界面
var tree: TreeView # 天赋树子界面
var codex: CodexView # 奖励格图鉴子界面(占位挂起态)
var esc_overlay: CampEscOverlay # Esc 浮窗(卡⑤——终态六枚落满)
+1
View File
@@ -0,0 +1 @@
uid://goaqtequmh8s
+3 -2
View File
@@ -1,4 +1,4 @@
## 模态层域句柄册:十三个模态覆盖层句柄+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。
## 模态层域句柄册:十二个平铺模态句柄+营地交互域子容器(camp)+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。
## RefCounted 不挂树;模态登记唯一口=register_layer(anchors 撑满宿主+排入 ModalStack)。
## flow 随容器构造即建(装配早期注入点经 root.ui.modals.flow.* 取 Callable——晚于 flow 建立时点)。
class_name UiModals
@@ -11,7 +11,7 @@ var level_screen: LevelMapScreen
var curio_overlay: CurioPickOverlay
var end_overlay: BattleEndOverlay
var shop_overlay: ShopOverlay
var camp_overlay: CampOverlay
var camp: UiCampModals
var settings_overlay: SettingsOverlay
var status_panel: StatusPanel
var career_overlay: CareerOverlay
@@ -22,6 +22,7 @@ var debug_overlay: DebugOverlay
func _init() -> void:
flow = ModalFlow.new() # 查询/钩子 Callable 由 _build_modal_flow 注入(注入时点不变——注入值面向已在座实例)
camp = UiCampModals.new() # 营地域句柄子容器(分域纪律——营地域句柄禁平铺回本册)
func register_layer(m: Control, tags: Array = []) -> void:
+12 -2
View File
@@ -22,7 +22,12 @@ const LAYER_CONTRACT: Array[Dictionary] = [
{"key": "curio_overlay", "kind": "modal", "tags": []},
{"key": "end_overlay", "kind": "modal", "tags": []},
{"key": "shop_overlay", "kind": "modal", "tags": []},
{"key": "camp_overlay", "kind": "modal", "tags": [], "shell": "camp_screen"},
{"key": "camp_scene", "kind": "modal", "tags": [], "shell": "camp_screen"}, # 营地场景根(交互式场景图——卡④)
{"key": "forge_view", "kind": "modal", "tags": []}, # 营地四子界面(独立全屏页——场景根留底形态,卡④ T3)
{"key": "treasure_view", "kind": "modal", "tags": []},
{"key": "tree_view", "kind": "modal", "tags": []},
{"key": "codex_view", "kind": "modal", "tags": []},
{"key": "camp_esc_overlay", "kind": "modal", "tags": []}, # Esc 浮窗(卡⑤——登记于营地层与设置层之间,契约 3 栈语义正解)
{"key": "settings_overlay", "kind": "modal", "tags": []},
{"key": "status_panel", "kind": "modal", "tags": []},
{"key": "career_overlay", "kind": "modal", "tags": []},
@@ -58,7 +63,12 @@ func node(key: String) -> Node:
"end_overlay": return modals.end_overlay
"shop_overlay": return modals.shop_overlay
"camp_screen": return screens.camp_screen
"camp_overlay": return modals.camp_overlay
"camp_scene": return modals.camp.scene
"forge_view": return modals.camp.forge
"treasure_view": return modals.camp.treasure
"tree_view": return modals.camp.tree
"codex_view": return modals.camp.codex
"camp_esc_overlay": return modals.camp.esc_overlay
"settings_overlay": return modals.settings_overlay
"status_panel": return modals.status_panel
"career_overlay": return modals.career_overlay
+13 -6
View File
@@ -203,15 +203,11 @@ func set_world_font(f: Font) -> void:
# D 区可用面积(AP 条之外)→ 格边长 = floor((min(宽, 高) − 预留 − (N−1)×格间距) / N)(battle.md 格尺寸公式)
# 公式须补算 (N−1)×2px 网格间距与垂直预留 24px,否则 12×12 板实高溢出可用区 ~6px,下缘被 clip_contents 裁掉。
# 算式唯一落点 = _cell_for(迭代 46 卡C——探针与单元测试共用该静态函数求值,禁复写算式造第二口径)。
func set_cell_area(avail: Vector2) -> void:
if _board == null or _board.width == 0:
return
var n: int = maxi(_board.width, _board.height)
var sep := SEP # 格间距(_init 同值)
var d := DEPTH_PX
var reserve := 24.0 + d # 垂直总预留(上 12 + 下 12 + 悬崖外延——悬崖占预算防溢出)
var cell := int(floor((minf(avail.x - 12.0 - d, avail.y - reserve) - float((n - 1) * sep)) / float(n)))
_cell = float(clampi(cell, 26, 46))
_cell = float(_cell_for(maxi(_board.width, _board.height), avail))
_hero_sprite.set_cell_size(_cell, float(SEP))
var stride := _cell + float(SEP)
for c in _cells:
@@ -224,6 +220,17 @@ func set_cell_area(avail: Vector2) -> void:
_update_world_size()
# 格边长算式唯一落点(迭代 46 卡C T1 自 set_cell_area 抽出——逐位同式:SEP/DEPTH_PX 读本文件
# 既有常量、clampi [26,46] 留本函数内;名字带下划线是 S13 max-public-methods 27 顶格的约束,禁改公开名。
# 探针(非当前板型 13/14×实板可用面积)与单元测试(四档板型)经本函数求值,复写算式即造第二口径)
static func _cell_for(n: int, avail: Vector2) -> int:
var sep := SEP # 格间距(_init 同值)
var d := DEPTH_PX
var reserve := 24.0 + d # 垂直总预留(上 12 + 下 12 + 悬崖外延——悬崖占预算防溢出)
var cell := int(floor((minf(avail.x - 12.0 - d, avail.y - reserve) - float((n - 1) * sep)) / float(n)))
return clampi(cell, 26, 46)
# 悬崖参数下发(网格像素尺寸/步距/列数/材质锚色——BoardWorld._draw 消费)
func _refresh_slab() -> void:
if _board == null or _board.width == 0:
@@ -0,0 +1,32 @@
## Esc 浮窗视图(营地语境 Esc 唤起):返回主菜单/设置/退出游戏三钮(裁决 9——三项且仅三项)。
## 壳=UiKit.modal_shell(居中模态壳,ui_kit 工厂单点);退出钮=UiKit.danger_button;零新素材零新色值。
## Q2 再按 Esc 关窗=组合根 Esc 路由直呼 flow.close(本视图无自关通路);三钮 pressed 外发意图不内含动线。
## 零动效(契约 6——直开直关,子树零 AnimationPlayer、源码零动效令牌)。
class_name CampEscOverlay
extends Control
signal return_requested ## 返回主菜单意图(动线归 MainEscFlow._on_return_to_menu——首步 close 浮窗)
signal settings_requested ## 设置意图(开设置层——浮窗登记在其下,栈语义关后重现)
signal quit_requested ## 退出游戏意图(_quit_game 原样复用)
var _return_btn: Button
var _settings_btn: Button
var _quit_btn: Button
func setup_buttons() -> void:
var box: VBoxContainer = UiKit.modal_shell(self, Color(UiKit.DIM, 0.55), Vector2(360, 0), 12,
UiKit.panel_skinned(Vector4(14, 12, 14, 12))).box
_return_btn = UiKit.button("返回主菜单")
box.add_child(_return_btn)
_settings_btn = UiKit.button("设置")
box.add_child(_settings_btn)
_quit_btn = UiKit.danger_button("退出游戏")
box.add_child(_quit_btn)
visible = false # 登记模态初始隐(开合走栈——模态纪律)
func bind_signals() -> void:
_return_btn.pressed.connect(func() -> void: return_requested.emit())
_settings_btn.pressed.connect(func() -> void: settings_requested.emit())
_quit_btn.pressed.connect(func() -> void: quit_requested.emit())
@@ -0,0 +1 @@
uid://col25elh1utw5
+27
View File
@@ -0,0 +1,27 @@
## 营地顶栏小 HUD 栏(裁决 10——钥石余额常驻,最小集形态):承载 UiKit.keystone_badge(工厂单点)。
## 读数只经 camp_data() 投影单口(禁第二账本、禁视图自算);刷新挂卡④ CampRefreshBus 单口
## (bus 目标册成员——显式触发才变,禁 _ready 自刷/自持第二入口,契约 5);载体=PRESET_TOP_WIDE 48px 浮层。
class_name CampHudBand
extends Control
var _badge: PanelContainer
func _init() -> void:
set_anchors_preset(Control.PRESET_TOP_WIDE)
custom_minimum_size = Vector2(0, 48) # 高 48px 浮层覆盖、不挤压画面(camp.md §顶栏)
mouse_filter = Control.MOUSE_FILTER_IGNORE # HUD 栏不挡场景命中区(浮层覆盖语义)
_badge = UiKit.keystone_badge("钥石 0")
_badge.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
_badge.position = Vector2(-160, 8) # 顶栏右位次(UiKit 现行件域内——代拍板 3)
add_child(_badge)
## 余额写入(格式串=既有消费面同款「钥石 %d」——badge 工厂约定单点,禁第二格式串)
func set_balance(v: int) -> void:
_badge.value_label.text = "钥石 %d" % v
## 刷新单口落点(CampRefreshBus 目标册成员——显式触发才变,契约 5 刷新时机①②③同一闸)
func refresh_camp(camp_data: Dictionary) -> void:
set_balance(int(camp_data.get("balance", 0)))
@@ -0,0 +1 @@
uid://by6ugjfdicgud
@@ -1,655 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 营地覆盖层:树淬炼/锻造工坊/秘宝匣/奖励格图鉴四柜台只读投影,购买与切换意图经信号外发。
## 柜台锁定态(钥匙未点亮)开/关零副作用——零写档零报错、锁定行纯文案无可交互购买路径。
## 开合与开始层互斥归组合根承接(打开前隐藏开始层)。
## 内部状态件(u1 子域拆件——契约 9 职责轴):_TreeState=树淬炼柜台内容容器(单一变化原因=树行板结构);
## _ForgeState=锻造工坊与秘宝匣两柜台行板与锁定门(钥匙门同构);_AttuneState=同调装配段节点与钮册;
## _SideState=Y 刷新升级行与最近冒险账面。探针直读冻结成员(_balance_label/_tree_buy_btns/
## _tree_fork_btns/_workshop_holder/_workshop_rows)住宿主不进状态件。
class_name CampOverlay
extends Control
signal buy_requested(attr: String)
signal closed
signal attune_toggled(item_id: String) ## 同调装配切换意图(写口唯一归 MetaProgression.toggle_attunement)
signal tree_buy_requested(node_id: String) ## 天赋树购买意图(写口唯一归 MetaProgression.tree_buy)
signal tree_fork_toggled(group: String, side: String) ## 双态切换意图(一节点双态——零币切态)
signal forge_buy_requested(route_id: String) ## 锻造路线购买意图(写口唯一归 MetaProgression.forge_buy_level)
signal forge_unlock_requested(route_id: String) ## 锻造路线解锁意图(写口唯一归 MetaProgression.forge_buy_unlock)
signal forge_select_requested(route_id: String) ## 锻造路线选中意图(写口唯一归 MetaProgression.forge_select)
# 方向稿取色(柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit
const HEADER_BAND_COL := UiKit.CAMP_HEADER_BAND
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
class _TreeState extends RefCounted:
var _tree_holder: VBoxContainer # 树淬炼柜台内容(主界面——脊柱两行+钥匙/socket 行板+Fork 双态钮)
class _ForgeState extends RefCounted:
var _forge_lock_l: Label # 锻造工坊柜台锁定态行(钥匙未点亮)
var _forge_workbench: VBoxContainer # 锻造工坊工作间(钥匙点亮后展开——两区六路线行板)
var _forge_zone_weapon: VBoxContainer # 武器锻造区行板容器(三路线)
var _forge_zone_armor: VBoxContainer # 护甲锻造区行板容器(三路线)
var _forge_rows: Array[Dictionary] = [] # [{route_id, name_l, lvl_l, status_l, buy_btn, unlock_btn, select_btn, next_price, unlock_price}](open/refresh 重建;探针消费)
var _ws_scroll: ScrollContainer # 秘宝匣柜台内容容器(钥匙点亮后可见)
var _ws_lock_l: Label # 秘宝匣柜台锁定态行
class _AttuneState extends RefCounted:
var _attune_header_l: Label # 同调装配段(Slots 点亮呈现)
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn}]——装配/卸下切换钮(满位禁选随刷)
var _attune_nodes: Array[Control] = [] # 段内节点册(open_camp 重建时定向清理,不扰工坊六行)
class _SideState extends RefCounted:
var _refresh_l: Label # Y 刷新升级行(双入口之一——树上 ks_refresh 同账)
var _refresh_btn: Button
var _recent_list: Label # 最近冒险(逐行拼接 label——只读账面,随 open_camp 重建)
var _balance_label: Label # 探针直读冻结(S17①——住宿主)
var _tree_buy_btns: Array[Dictionary] = [] # [{node_id, btn}]——树购买/解锁钮登记面(open/refresh 重建时刷新;探针消费;住宿主)
var _tree_fork_btns: Array[Dictionary] = [] # [{group, side, btn}]——Fork 双态切换钮登记面(探针消费;住宿主)
var _workshop_holder: VBoxContainer # 秘宝匣柜台行板(六行+同调装配段+Y 升级行;探针直读冻结——住宿主)
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l}](探针消费;住宿主)
var _tree := _TreeState.new()
var _forge := _ForgeState.new()
var _attune := _AttuneState.new()
var _side := _SideState.new()
func _init() -> void:
# 底=不透明暖亮档(半透 dim 会透出底下棋盘/战斗提示条,故须实底;demo #v-camp=bg-bright 实底,
# 遮蔽语义由组合根输入锁兜底)
var dim := UiKit._modal_dim(self, UiKit.BG_BRIGHT)
# 石纹弱质感+暗角(demo .tilebg 0.14/.vignette;缺位不挂即回退平色)
var bd := UiKit.backdrop_tile()
if bd != null:
dim.add_child(bd)
dim.add_child(UiKit.vignette_rect())
var root := MarginContainer.new()
root.set_anchors_preset(Control.PRESET_FULL_RECT)
root.add_theme_constant_override("margin_left", 16)
root.add_theme_constant_override("margin_right", 16)
root.add_theme_constant_override("margin_top", 12)
root.add_theme_constant_override("margin_bottom", 12)
dim.add_child(root)
var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 8)
root.add_child(col)
# ── 顶栏:左铭牌(标题+副题)/ 右钥石徽 ──
_build_top_bar(col)
# ── 四柜台并排(程序占位重排)──
# 宽度不足横向滚动(四柜 300 min 宽 > 窄窗可用宽时横滚兜底;纵向撑满余高)
var camp_scroll := ScrollContainer.new()
camp_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
camp_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
camp_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
col.add_child(camp_scroll)
var mid := HBoxContainer.new()
mid.add_theme_constant_override("separation", 14)
mid.size_flags_vertical = Control.SIZE_EXPAND_FILL
camp_scroll.add_child(mid)
_build_tree_counter(mid)
_build_forge_counter(mid)
_build_workshop_counter(mid)
_build_atlas_counter(mid)
_build_bottom_bar(col)
visible = false
## 顶栏域:铭牌(标题+副题)+钥石徽章
func _build_top_bar(col: VBoxContainer) -> void:
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 10)
col.add_child(top)
var plaque := PanelContainer.new()
plaque.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8)))
top.add_child(plaque)
var plaque_col := VBoxContainer.new()
plaque.add_child(plaque_col)
var title := UiKit.label("营地 · 天赋树",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
plaque_col.add_child(title)
var sub := UiKit.label("营地本身即树——钥石逐节点点亮,属性/规则/工作间都在树上",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # STONE 对铭牌板 3.59:1 越带,换灰石深化 5.98:1
plaque_col.add_child(sub)
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(spacer)
# 钥石徽章烛光黄底+深墨字(demo ②)。UiKit.keystone_badge 工厂单点
var badge := UiKit.keystone_badge("钥石 0")
top.add_child(badge)
_balance_label = badge.value_label
## 柜一·树淬炼(树脊柱界面形态=主界面:一线一节点行板内升级+Fork 双态切换钮)
func _build_tree_counter(mid: HBoxContainer) -> void:
_tree._tree_holder = VBoxContainer.new()
_tree._tree_holder.add_theme_constant_override("separation", 6)
var tree_scroll := ScrollContainer.new()
tree_scroll.custom_minimum_size = Vector2(300, 336)
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
tree_scroll.add_child(_tree._tree_holder)
var tree_panel := _counter_panel("天赋树 · 淬炼", "", tree_scroll, 300)
tree_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
tree_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
mid.add_child(tree_panel)
## 柜二·锻造工坊(钥匙门——forge_workshop 点亮前锁定态;点亮=工作间两区六路线行板)
func _build_forge_counter(mid: HBoxContainer) -> void:
_forge._forge_lock_l = UiKit.label("🔒 锻造工坊未开放\n\n点亮树上「锻造工坊」钥匙节点后展开工作间。\n(钥匙免费、无前置——树上直接点亮)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
_forge._forge_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER # 锁文在等高栏内垂直居中
_forge._forge_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_forge._forge_lock_l.custom_minimum_size = Vector2(280, 0)
_forge._forge_workbench = VBoxContainer.new()
_forge._forge_workbench.add_theme_constant_override("separation", 6)
_forge._forge_workbench.visible = false # 默认锁定态(钥匙门未判前零行板——门开才建)
_forge._forge_zone_weapon = VBoxContainer.new()
_forge._forge_zone_weapon.add_theme_constant_override("separation", 4)
_forge._forge_zone_armor = VBoxContainer.new()
_forge._forge_zone_armor.add_theme_constant_override("separation", 4)
var wb_wh := UiKit.label("── 武器锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK) # 同款深金墨(对柜台板 5.21:1)
_forge._forge_workbench.add_child(wb_wh)
_forge._forge_workbench.add_child(_forge._forge_zone_weapon)
var wb_ah := UiKit.label("── 护甲锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK)
_forge._forge_workbench.add_child(wb_ah)
_forge._forge_workbench.add_child(_forge._forge_zone_armor)
var forge_holder := VBoxContainer.new()
forge_holder.add_theme_constant_override("separation", 6)
forge_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(与树/秘宝匣 scroll 336 min 对齐——锁定态仅锁文也撑住栏高)
forge_holder.add_child(_forge._forge_lock_l)
forge_holder.add_child(_forge._forge_workbench)
var forge_panel := _counter_panel("锻造工坊", UiKit.TEX_ICON_FORGE, forge_holder, 300)
forge_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
forge_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
mid.add_child(forge_panel)
## 柜三·秘宝匣(钥匙门——ks_workshop 点亮前锁定态;点亮=现工坊内容整体迁入:六行+同调装配段+Y 升级行)
func _build_workshop_counter(mid: HBoxContainer) -> void:
_forge._ws_lock_l = UiKit.label("🔒 秘宝匣未开放\n\n点亮树上「秘宝匣」钥匙节点后展开。\n(前置=脊柱生命第 1 级)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
_forge._ws_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER # 锁文在等高栏内垂直居中
_forge._ws_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_forge._ws_lock_l.custom_minimum_size = Vector2(280, 0)
_forge._ws_scroll = ScrollContainer.new()
_forge._ws_scroll.custom_minimum_size = Vector2(300, 336)
_forge._ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_forge._ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
_workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 6)
_forge._ws_scroll.add_child(_workshop_holder)
var ws_holder := VBoxContainer.new()
ws_holder.add_theme_constant_override("separation", 6)
ws_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(锁定态 scroll 隐、仅锁文也撑住栏高)
ws_holder.add_child(_forge._ws_lock_l)
ws_holder.add_child(_forge._ws_scroll)
var ws_panel := _counter_panel("秘宝匣", UiKit.TEX_ICON_WORKSHOP, ws_holder, 300)
ws_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
ws_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
mid.add_child(ws_panel)
## 柜四·奖励格图鉴(常开独立位——结构位预留,内容排 Alpha)
func _build_atlas_counter(mid: HBoxContainer) -> void:
var atlas_holder := VBoxContainer.new()
atlas_holder.custom_minimum_size = Vector2(0, 336) # 四栏等高(图鉴仅锁文也撑住栏高)
var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 300)
atlas_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
atlas_panel.size_flags_vertical = Control.SIZE_FILL # 四栏等高:任一栏长高时四栏同步拉伸
mid.add_child(atlas_panel)
var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)",UiKit.FS_HINT, false, UiKit.STONE_DEEP)
lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER # 锁文在等高栏内垂直居中
lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lock_l.custom_minimum_size = Vector2(280, 0)
atlas_holder.add_child(lock_l)
## 底栏域:关闭钮(左)/ 操作注记(右)+ 最近冒险条带
func _build_bottom_bar(col: VBoxContainer) -> void:
var close_btn := UiKit.button("关闭营地(回开始界面)")
close_btn.pressed.connect(_emit_closed)
var bottom := HBoxContainer.new()
bottom.add_theme_constant_override("separation", 10)
bottom.add_child(close_btn)
var spacer2 := Control.new()
spacer2.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bottom.add_child(spacer2)
var caption := UiKit.label("树上点亮 · 工作间随钥匙展开 · 图鉴常开",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
bottom.add_child(caption)
col.add_child(bottom)
_side._recent_list = UiKit.label("",UiKit.FS_HINT, false, UiKit.WARM_SOFT)
_side._recent_list.clip_text = true
_side._recent_list.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
col.add_child(_side._recent_list)
# 柜台列面板(方向稿同构):PanelContainer 米板身 + 头带深棕(图标+标题暖白)
func _counter_panel(title_text: String, icon_path: String, holder: Control, min_w: int) -> PanelContainer:
var panel := PanelContainer.new()
panel.custom_minimum_size = Vector2(min_w, 0)
panel.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 10, 10, 10)))
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 8)
panel.add_child(vbox)
var band := PanelContainer.new()
band.add_theme_stylebox_override("panel", UiKit.panel_flat(HEADER_BAND_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 5, 10, 5)))
vbox.add_child(band)
var head := HBoxContainer.new()
head.add_theme_constant_override("separation", 6)
band.add_child(head)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, 32) # 柜台头带 64 源 0.5×;缺位 null → 纯文本回退
if ic != null:
head.add_child(ic)
var l := UiKit.label(title_text,UiKit.FS_BODY, true, UiKit.WARM)
head.add_child(l)
vbox.add_child(holder)
return panel
# ---- 柜台行构建(open_camp 重建;行结构仿 ShopOverlay 商品卡——亮板双色墨制)----
# 天赋树段重建(树淬炼柜台主界面:脉分组行板:节点名/等级/价/购买钮 + Fork 双态切换钮;
# 树数据 = 组合根 _camp_data().tree 投影 [{node_id, display_name, vein, kind, level, price, can_buy,
# currency}...] + fork_groups [{group, node_id, active_side, owned, can_buy, price, sides:[{side, display_name, effects}]}])
func _rebuild_tree(data: Dictionary) -> void:
for c in _tree._tree_holder.get_children():
c.queue_free()
_tree_buy_btns.clear()
_tree_fork_btns.clear()
var tree: Array[Variant] = data.get("tree", []) as Array
var forks: Array[Variant] = data.get("fork_groups", []) as Array
var vein_names := {"spine": "·脊柱淬炼·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
var last_vein := ""
for row in tree:
var vein := String(row.get("vein", ""))
if vein != last_vein:
last_vein = vein
var vh := UiKit.label(String(vein_names.get(vein, vein)),UiKit.FS_HINT, true, UiKit.GOLD_INK) # GOLD_DEEP 对柜台板 2.67:1 越带,换深金墨 5.21:1
_tree._tree_holder.add_child(vh)
if String(row.get("kind", "")) == "fork":
continue # Fork 行去行板化——由 fork_groups 段承载双态切换钮
_tree._tree_holder.add_child(_tree_row_line(row))
for fg in forks:
_tree._tree_holder.add_child(_tree_fork_line(fg))
## 树行板构建域:节点名(含门槛标记)+购买钮(追加序与锚句随段整体搬迁)
func _tree_row_line(row: Dictionary) -> HBoxContainer:
var hb := HBoxContainer.new()
hb.add_theme_constant_override("separation", 6)
var lvl := int(row.get("level", 0))
var cap := int(row.get("cap", 1))
var name_l := UiKit.label("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, cap], 13, false, UiKit.INK_LABEL)
name_l.custom_minimum_size = Vector2(150, 0)
name_l.clip_text = true
var gate := String(row.get("gate_text", ""))
if gate != "":
name_l.text += " 🔒" # 等级阈值前置未达标标记(R1 机制位——悬停看门槛;达标后随 refresh 消失)
name_l.tooltip_text = "等级门槛:%s" % gate
hb.add_child(name_l)
var btn := UiKit.button("购买 %d 钥石" % int(row.get("price", 0)), 0.0) # 柜台行板紧凑钮豁免宽度基准
btn.pressed.connect(func(): tree_buy_requested.emit(String(row.get("node_id", ""))))
btn.disabled = not bool(row.get("can_buy", false))
hb.add_child(btn)
_tree_buy_btns.append({"node_id": String(row.get("node_id", "")), "btn": btn})
return hb
## Fork 双态行板构建域:组状态文案+解锁钮或双态切换钮
func _tree_fork_line(fg: Dictionary) -> HBoxContainer:
var group := String(fg.get("group", ""))
var active := String(fg.get("active_side", ""))
var owned := bool(fg.get("owned", false))
var fb := HBoxContainer.new()
fb.add_theme_constant_override("separation", 6)
var state_txt := "未解锁"
if owned:
state_txt = "激活:%s" % active
var gl := UiKit.label("%s[%s]" % [String(fg.get("display_name", group)), state_txt], 13, false, UiKit.INK_LABEL)
fb.add_child(gl)
if not owned:
var bb := UiKit.button("解锁 %d 钥石" % int(fg.get("price", 0)), 0.0) # 行板紧凑钮豁免
bb.pressed.connect(func(): tree_buy_requested.emit(String(fg.get("node_id", ""))))
bb.disabled = not bool(fg.get("can_buy", false))
fb.add_child(bb)
_tree_buy_btns.append({"node_id": String(fg.get("node_id", "")), "btn": bb})
else:
for side_row in (fg.get("sides", []) as Array):
var sd := String(side_row.get("side", ""))
var sb := UiKit.button(String(side_row.get("display_name", sd)), 0.0) # 行板紧凑钮豁免
sb.pressed.connect(func(): tree_fork_toggled.emit(group, sd))
fb.add_child(sb)
_tree_fork_btns.append({"group": group, "side": sd, "btn": sb})
return fb
# 工作间钥匙门(锻造工坊/秘宝匣柜台锁定态切换:节点点亮=展开、未点亮=锁定行;
# 图鉴常开无门槛。判据读树投影行 level——零核心访问,纯视图)
func _refresh_key_gates(data: Dictionary) -> void:
var lvl_by_id := {}
for row in (data.get("tree", []) as Array):
lvl_by_id[String(row.get("node_id", ""))] = int(row.get("level", 0))
var forge_open := int(lvl_by_id.get("forge_workshop", 0)) >= 1
var ws_open := int(lvl_by_id.get("ks_workshop", 0)) >= 1
_forge._forge_lock_l.visible = not forge_open
_forge._forge_workbench.visible = forge_open
_forge._ws_lock_l.visible = not ws_open
_forge._ws_scroll.visible = ws_open
# 锻造工坊行板重建(两区六路线;open_camp/refresh_camp 调用。行结构仿秘宝匣行板:
# 件名+等级+解锁态+购买/解锁/选中钮。UI 只发意图信号〔契约 9——零核心写调用、零 MetaProgression
# 持有〕;钮禁用=常态防线(余额/未解锁/满级/已选中),兜底拒绝可见归 presenter pattern 1)
func _rebuild_forge(data: Dictionary) -> void:
for zone in [_forge._forge_zone_weapon, _forge._forge_zone_armor]:
for c in zone.get_children():
c.queue_free()
_forge._forge_rows.clear()
if not _forge._forge_workbench.visible:
return # 锁定态(钥匙未点亮):零行板零购买路径——占位纪律延续
var bal := int(data.get("balance", 0))
for row in data.get("forge", []):
var kind := String(row.get("kind", ""))
var zone: VBoxContainer = _forge._forge_zone_weapon if kind == "weapon" else _forge._forge_zone_armor
var card := _forge_route_card(row, bal)
zone.add_child(card["card"])
_forge._forge_rows.append(card)
## 锻造行板构建域:单路线卡(件名+等级+状态+购买/解锁/选中钮——并联双通道呈现面)
func _forge_route_card(row: Dictionary, bal: int) -> Dictionary:
var d := _forge_route_scalars(row)
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 6)
card.add_child(line)
var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(104, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(name_l)
var lvl_l := UiKit.label("Lv%d/%d" % [d["lvl"], d["cap_v"]], 13, false, UiKit.INK_LABEL)
lvl_l.custom_minimum_size = Vector2(44, 0)
line.add_child(lvl_l)
var status_l := UiKit.label(_forge_status_text(d, row), UiKit.FS_HINT, false, UiKit.STONE_DEEP)
status_l.clip_text = true
status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
line.add_child(status_l)
var btns := _forge_route_buttons(d, row, bal, line)
return {"card": card, "route_id": d["rid"], "name_l": name_l, "lvl_l": lvl_l, "status_l": status_l,
"buy_btn": btns["buy_btn"], "unlock_btn": btns["unlock_btn"], "select_btn": btns["select_btn"],
"next_price": d["next_price"], "unlock_price": d["unlock_price"]}
## 锻造行标量束:路线 id/解锁态/初始持有/等级/cap/几何价/解锁价一次取数
func _forge_route_scalars(row: Dictionary) -> Dictionary:
return {
"rid": String(row.get("route_id", "")),
"unlocked": bool(row.get("unlocked", false)),
"initial": bool(row.get("initial_owned", false)),
"lvl": int(row.get("level", 0)),
"cap_v": int(row.get("cap", 0)),
"next_price": int(row.get("next_price", 0)),
"unlock_price": int(row.get("unlock_price", 0)),
}
## 状态文案域(并联双通道:练满前一档 or 一次性解锁价直购;已满级态)
func _forge_status_text(d: Dictionary, row: Dictionary) -> String:
var st_text := "已解锁"
if not d["unlocked"]:
if d["initial"]:
st_text = "未解锁"
else:
st_text = "未解锁 · 练满%s 或 %d 钥石直购" % [String(row.get("prereq_name", "")), d["unlock_price"]]
elif d["lvl"] >= d["cap_v"]:
st_text = "已满级"
return st_text
## 三钮域:购买(已解锁未满级——余额不足禁用)/解锁(待解锁非初始——余额不足禁用)/选中(已解锁——当前=禁用态标记)
func _forge_route_buttons(d: Dictionary, row: Dictionary, bal: int, line: HBoxContainer) -> Dictionary:
var buy_btn: Button = null
if d["unlocked"] and d["lvl"] < d["cap_v"]:
buy_btn = UiKit.button("锻造 %d" % d["next_price"], 0.0) # 行板紧凑钮豁免
buy_btn.pressed.connect(forge_buy_requested.emit.bind(d["rid"]))
buy_btn.disabled = bal < d["next_price"]
line.add_child(buy_btn)
var unlock_btn: Button = null
if not d["unlocked"] and not d["initial"]:
unlock_btn = UiKit.button("解锁 %d" % d["unlock_price"], 0.0) # 行板紧凑钮豁免
unlock_btn.pressed.connect(forge_unlock_requested.emit.bind(d["rid"]))
unlock_btn.disabled = bal < d["unlock_price"]
line.add_child(unlock_btn)
var select_btn: Button = null
if d["unlocked"]:
var sel := bool(row.get("selected", false))
select_btn = UiKit.button("当前" if sel else "选中", 0.0) # 行板紧凑钮豁免
select_btn.pressed.connect(forge_select_requested.emit.bind(d["rid"]))
select_btn.disabled = sel
line.add_child(select_btn)
return {"buy_btn": buy_btn, "unlock_btn": unlock_btn, "select_btn": select_btn}
# 打开营地(全量重建四柜台行 + 同调装配段 + 最近冒险;树段与钥匙门随 refresh_camp 重建——
# 数据 = 组合根 _camp_data() 投影)
func open_camp(data: Dictionary) -> void:
_rebuild_workshop(data) # 段尾链 _rebuild_attune(同调装配段+Y 升级行随秘宝匣柜台内容)
_rebuild_forge(data) # 锻造工坊两区六路线行板
_rebuild_recent(data)
refresh_camp(data)
visible = true
# 状态重刷(购买后调用——余额/树行板/钥匙门/工坊状态就地重建或改文案;树行板随刷重建=按钮
# 禁用态与门槛/cap 同步,12 行重建开销可忽略)
func refresh_camp(data: Dictionary) -> void:
var balance := int(data.get("balance", 0))
_balance_label.text = "钥石 %d" % balance
_rebuild_tree(data)
_refresh_key_gates(data)
_rebuild_forge(data) # 行板随刷重建(购买/解锁/选中后等级/价/禁用态同步)
_refresh_workshop_rows(data, balance)
_refresh_attune_segment(data, balance)
## 秘宝匣行板刷新域:进度列(解锁混合制三形态)与状态色(亮板双色墨制)与购买钮余额联动
func _refresh_workshop_rows(data: Dictionary, balance: int) -> void:
var ws_by_id := {}
for row in data.get("workshop", []):
ws_by_id[String(row.get("item_id", ""))] = row
for card in _workshop_rows:
var row: Dictionary[Variant, Variant] = ws_by_id.get(String(card.item_id), {})
if row.is_empty():
continue
var unlocked := bool(row.get("unlocked", false))
card.progress_l.text = _workshop_progress_text(row, unlocked)
# 状态色用亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读)
var st_text := "未解锁"
var st_col: Color = UiKit.STONE_DEEP
if unlocked:
st_text = "已解锁"
st_col = UiKit.GOLD_INK # GOLD_DEEP 对行板 3.00:1 越带,换深金墨 5.84:1
card.status_l.text = st_text
card.status_l.add_theme_color_override("font_color", st_col)
card.status_l.tooltip_text = st_text # 截断兜底(状态列 EXPAND_FILL 吸余宽——悬停看全文)
# 购买钮余额联动(禁用态=余额不足,常态防线;pattern 1 兜底拒绝可见归 presenter)
if card.get("buy_btn") != null:
card.buy_btn.disabled = balance < int(card.get("price", 0))
## 进度列文案域(解锁混合制三形态:钥石购买价/条件进度拼接/已入手)
func _workshop_progress_text(row: Dictionary, unlocked: bool) -> String:
if unlocked:
return "已入手"
var mode := String(row.get("mode", ""))
if mode == "keystone":
return "钥石 %d" % int(row.get("price", 0))
if mode == "condition":
var parts: Array[String] = []
for req_v in (row.get("reqs", []) as Array):
var req: Dictionary[Variant, Variant] = req_v as Dictionary
var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件"))
parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))])
return "、".join(parts)
return ""
## 同调装配段刷新域(Slots 点亮/满位禁选/Y 升级行余额联动——就地刷)
func _refresh_attune_segment(data: Dictionary, balance: int) -> void:
var att: Dictionary[Variant, Variant] = data.get("attune", {})
if _attune._attune_header_l != null:
var slots_n := int(att.get("slots", 0))
var eq: Array[Variant] = att.get("equipped", [])
_attune._attune_header_l.text = "── 同调装配 Slots %d/%d ──" % [eq.size(), slots_n]
var eq_set := {}
for v in eq:
eq_set[String(v)] = true
for card2 in _attune._attune_btns:
var on: bool = eq_set.has(String(card2.item_id))
card2.btn.text = "卸下" if on else "装配"
# 满位禁选+未解锁禁装(已装件恒可卸下;unlocked 快照随 rebuild 刷新)
card2.btn.disabled = (not on and eq.size() >= slots_n) or (not on and not bool(card2.get("unlocked", false)))
if _side._refresh_l != null:
var rf: Dictionary[Variant, Variant] = att.get("refresh", {})
_side._refresh_l.text = "三选一刷新 +%d 次/冒险 · Lv%d" % [int(rf.get("charges", 0)), int(rf.get("level", 0))]
_side._refresh_btn.text = "已满级" if bool(rf.get("maxed", false)) else "升级 %d 钥石" % int(rf.get("price", 0))
_side._refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0))
func _rebuild_workshop(data: Dictionary) -> void:
_workshop_rows.clear()
for child in _workshop_holder.get_children():
child.queue_free()
_attune._attune_nodes.clear()
_attune._attune_btns.clear()
_attune._attune_header_l = null
_side._refresh_l = null
_side._refresh_btn = null
for row in data.get("workshop", []):
var card := _workshop_card(row)
_workshop_holder.add_child(card["card"])
_workshop_rows.append(card)
_rebuild_attune(data)
## 秘宝匣行板构建域:件名(分档后缀+效果摘要悬停)+进度列+状态列+钥石购买钮(购买件未解锁在列)
func _workshop_card(row: Dictionary) -> Dictionary:
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 12)
card.add_child(line)
var tier_name := String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), ""))
var name_l := UiKit.label(String(row.get("display_name", "")) + ("·" + tier_name if tier_name != "" else ""),UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(132, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
name_l.tooltip_text = String(row.get("desc", "")) # 件效果摘要悬停可见(分档呈现契约——槽位档位=名后缀)
line.add_child(name_l)
var progress_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE_DEEP) # 同行价格墨同案
progress_l.custom_minimum_size = Vector2(150, 0)
progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜
progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(progress_l)
# 状态色用亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式
var status_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.STONE_DEEP)
status_l.clip_text = true # 同上——「已解锁 · 能力封存中」不撑柜(余宽吸收者)
status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
line.add_child(status_l)
# 钥石购买钮(购买件且未解锁时在列;写口唯一=buy_treasure_unlock,
# 经 buy_requested(item_id) 信号走 presenter 分支;禁用态=余额不足)
var buy_btn: Button = null
if not bool(row.get("unlocked", false)) and String(row.get("mode", "")) == "keystone":
buy_btn = UiKit.button("购买 %d" % int(row.get("price", 0)), 0.0) # 行板紧凑钮豁免
buy_btn.pressed.connect(_emit_buy_requested.bind(String(row.get("item_id", ""))))
line.add_child(buy_btn)
return {"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l, "buy_btn": buy_btn, "price": int(row.get("price", 0)), "unlocked": bool(row.get("unlocked", false)), "card": card}
# 同调装配段重建(秘宝匣柜台尾部段;Slots 点亮+
# 九件行板+Y 升级行,程序占位面)
func _rebuild_attune(data: Dictionary) -> void:
var att: Dictionary[Variant, Variant] = data.get("attune", {})
_attune._attune_header_l = UiKit.label("",UiKit.FS_HINT, true, UiKit.INK)
_workshop_holder.add_child(_attune._attune_header_l)
_attune._attune_nodes.append(_attune._attune_header_l)
for row in att.get("items", []):
var line := _attune_item_line(row)
_workshop_holder.add_child(line)
_attune._attune_nodes.append(line)
_refresh_attune_tail(att)
## 同调件行板构建域:件名+形态+档位+装配切换钮(未解锁禁装——toggle 写口同守卫)
func _attune_item_line(row: Dictionary) -> HBoxContainer:
var iid := String(row.get("item_id", ""))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 6)
var name_l := UiKit.label(String(row.get("display_name", "")),UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(96, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(name_l)
var form_l := UiKit.label("被动" if String(row.get("form", "")) == "passive" else "主动·充能",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
form_l.custom_minimum_size = Vector2(58, 0)
line.add_child(form_l)
# 档位标签(槽位档位显示:件档=普通/优秀/稀有,装入须件档≤格档契约 2 的呈现半边)
var tier_l := UiKit.label(String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), "—")),UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
tier_l.custom_minimum_size = Vector2(36, 0)
line.add_child(tier_l)
var btn := UiKit.button("装配", 0.0) # 行板紧凑钮豁免
btn.pressed.connect(attune_toggled.emit.bind(iid))
btn.disabled = not bool(row.get("unlocked", false)) # 未解锁禁装(解锁为装配前置——toggle 写口同守卫)
line.add_child(btn)
_attune._attune_btns.append({"item_id": iid, "btn": btn, "unlocked": bool(row.get("unlocked", false)), "equipped": bool(row.get("equipped", false))})
return line
## Y 升级尾行构建域(双入口之一——树上 ks_refresh 同账)
func _refresh_attune_tail(att: Dictionary) -> void:
var refresh_line := HBoxContainer.new()
refresh_line.add_theme_constant_override("separation", 6)
_side._refresh_l = UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
refresh_line.add_child(_side._refresh_l)
_side._refresh_btn = UiKit.button("升级", 0.0) # 行板紧凑钮豁免
_side._refresh_btn.pressed.connect(_emit_buy_requested.bind("attune_refresh"))
refresh_line.add_child(_side._refresh_btn)
_workshop_holder.add_child(refresh_line)
_attune._attune_nodes.append(refresh_line)
func _rebuild_recent(data: Dictionary) -> void:
var lines: Array[String] = []
var recent: Array[Variant] = data.get("recent", [])
if recent.is_empty():
lines.append(" 最近冒险:(暂无记录——完成或结束一次冒险后入账)")
for s in recent:
var verdict := "失败"
if String(s.get("result", "")) == "completed":
verdict = "完成"
elif String(s.get("result", "")) == "abandoned":
verdict = "放弃"
lines.append("最近冒险:%s · 完成 %d 关 · 钥石 +%d · 种子 %d" % [verdict,
int(s.get("levels_cleared", 0)), int(s.get("keystone_total", 0)), int(s.get("master_seed", 0))])
_side._recent_list.text = " | ".join(lines)
func _emit_closed() -> void:
closed.emit()
func _emit_buy_requested(attr: String) -> void:
buy_requested.emit(attr)
@@ -1 +0,0 @@
uid://dvg2wmjh87p8a
@@ -0,0 +1,17 @@
## 营地刷新单口:各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成(卡⑤ HUD 同口挂接)。
## 非 autoload、非信号广播——组合根单点推送(组合根建件绑册,presenter 经 context 持引用;
## 卡④ 契约 4:写口语义零改,只改路由形态)。
class_name CampRefreshBus
extends RefCounted
var _targets: Array[Control] = [] # 可刷面册(四子界面+卡⑤ HUD 栏——均带 refresh_camp(data))
func bind_targets(targets: Array[Control]) -> void:
_targets = targets
func refresh(camp_data: Dictionary) -> void:
for t in _targets:
if t != null and t.visible and t.has_method("refresh_camp"):
t.refresh_camp(camp_data) # 现开界面就地重刷(未开的零开销——留底形态下场景根恒开)
@@ -0,0 +1 @@
uid://cdfmyxf87n6f6
+309
View File
@@ -0,0 +1,309 @@
## 营地场景图视图(交互式营地):10 层贴图铺底+七物件命中区+点选反馈四态+顶栏预留位。
## 布局唯一事实源 = db.presentation.camp_scene_layout(构造注入,本视图禁自载表路径);
## 子节点序 = 表内数组序 = z 序(Control 根+TextureRect 层——禁 CanvasLayer/z_index/Sprite2D)。
## 输入单点:根 _gui_input 集中命中判定(层节点全 IGNORE),点击只发 object_pressed 信号,路由归组合根。
## 零核心写调用:视图不读 MetaProgression,禁用态由组合根经 set_feedback_state 推送。
class_name CampSceneView
extends Control
signal object_pressed(key: StringName) ## 命中区点击确认(press+release 同区且非禁用;组合根路由开界面/动线)
## 名牌文案(camp.md §场景构成绑定表——七可点件按界面名逐字)
const _HOTSPOT_NAMES := {
&"gate": "冒险", &"forge": "锻造工坊", &"chest": "秘宝匣", &"dummy": "天赋树",
&"board": "奖励格图鉴", &"notebook": "生涯记录", &"campfire": "离开营地",
}
## 层键→纹理路径(单点 = UiKit.TEX_CAMP_*,本表只引用不自写路径;缺位 load_tex→null→程序化占位)
const _TEX_BY_KEY := {
&"bg": UiKit.TEX_CAMP_BG, &"wall": UiKit.TEX_CAMP_WALL, &"board": UiKit.TEX_CAMP_BOARD,
&"gate": UiKit.TEX_CAMP_GATE, &"dummy": UiKit.TEX_CAMP_DUMMY, &"forge": UiKit.TEX_CAMP_FORGE,
&"tent": UiKit.TEX_CAMP_TENT, &"chest": UiKit.TEX_CAMP_CHEST,
&"notebook": UiKit.TEX_CAMP_NOTEBOOK, &"campfire": UiKit.TEX_CAMP_CAMPFIRE,
}
## 禁用态去饱和着色器(modulate 只能调明度——降明度+去饱和走 canvas_item shader,零新素材)
const _DESAT_SHADER_SRC := "
shader_type canvas_item;
uniform float strength : hint_range(0.0, 1.0) = 1.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
float g = dot(tex.rgb, vec3(0.299, 0.587, 0.114));
tex.rgb = mix(tex.rgb, vec3(g) * 0.72, strength);
COLOR = tex * COLOR;
}
"
enum FeedbackState { NORMAL, HOVER, PRESSED, DISABLED } ## 点选反馈四态(camp.md §点选反馈)
var _layout: CampSceneLayoutTable # 布局表(坐标唯一事实源)
var _layer_nodes: Dictionary[StringName, Control] = {} # 层键→TextureRect/ColorRect 占位(子节点序=z 序)
var _hotspot_rects: Dictionary[StringName, Rect2] = {} # 命中键→Rect2(视图局部坐标=表坐标)
var _hotspot_layer: Dictionary[StringName, StringName] = {} # 命中键→层键
var _base_states: Dictionary[StringName, int] = {} # 命中键→FeedbackState 值(持久基态——组合根置 DISABLED/NORMAL)
var _outline: Panel # 悬停轮廓亮边
var _name_label: Label # 物件名牌(下方 8px 居中,画面内钳制)
var _hover_key := &"" # 当前悬停命中键(&""=无)
var _press_key := &"" # 当前按住命中键
var _float_tween: Tween # 悬停浮动补间(逐级整像素吸附,禁 _process 逐帧移 UI)
var _desat_mat: ShaderMaterial # 禁用态共享材质(strength 恒 1,材质挂/摘承载开关)
var _hud_slot: Control # 顶栏预留位(卡⑤ set_hud_band 填值)
var hud_band: Control # 顶栏 HUD 栏句柄(卡⑤ CampHudBand——装配后 bus 目标册取用面)
func _init() -> void:
visible = false # 登记模态初始隐(开合走栈——模态纪律;boot 态场景根不得盖菜单)
func setup_layers(layout: CampSceneLayoutTable) -> void:
if layout == null:
push_error("[CampSceneView] 布局表缺位——组合根须注入 db.presentation.camp_scene_layout")
return
_layout = layout
mouse_filter = Control.MOUSE_FILTER_STOP # 场景图=营地语境主交互面(模态层在兄弟位先截事件)
_build_desat_material()
for spec in layout.layers:
var node: Control = _build_layer_node(spec)
add_child(node)
_layer_nodes[spec.key] = node
_build_outline()
_build_name_label()
_build_hud_slot(layout)
mouse_exited.connect(_on_mouse_exited) # 悬出根控件=清 hover/press(防态悬挂)
func bind_hotspots(layout: CampSceneLayoutTable) -> void:
if layout == null:
return
for spec in layout.layers:
if spec.is_clickable():
_hotspot_rects[spec.hotspot_key] = spec.hotspot
_hotspot_layer[spec.hotspot_key] = spec.key
_base_states[spec.hotspot_key] = FeedbackState.NORMAL
func get_hotspot_rect(key: StringName) -> Rect2:
var rect: Rect2 = _hotspot_rects.get(key, Rect2())
return rect
func set_feedback_state(key: StringName, state: FeedbackState) -> void:
if not _base_states.has(key):
return
_base_states[key] = state # 持久基态只收 NORMAL/DISABLED;HOVER/PRESSED 为视图内瞬态
_apply_state(key, _base_state(key))
func set_hud_band(band: Control) -> void:
if _hud_slot != null:
_hud_slot.add_child(band)
hud_band = band
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
_handle_motion(event as InputEventMouseMotion)
elif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
_handle_button(event as InputEventMouseButton)
accept_event()
func _handle_motion(event: InputEventMouseMotion) -> void:
var key: StringName = _topmost_hotspot_at(event.position)
if key != _hover_key:
if _hover_key != &"":
_apply_state(_hover_key, _base_state(_hover_key))
_hover_key = key
if key != &"":
_apply_state(key, FeedbackState.HOVER)
if _press_key != &"" and key != _press_key:
_apply_state(_press_key, _base_state(_press_key)) # 按住拖出=取消按压
_press_key = &""
func _handle_button(event: InputEventMouseButton) -> void:
var hit: StringName = _topmost_hotspot_at(event.position)
if event.pressed:
if hit != &"":
_press_key = hit
_apply_state(hit, FeedbackState.PRESSED)
return
if _press_key == &"":
return
if hit == _press_key and not _is_disabled(_press_key):
object_pressed.emit(_press_key)
_apply_state(_press_key, _base_state(_press_key))
_press_key = &""
func _build_layer_node(spec: CampSceneLayerSpec) -> Control:
var node: Control = null
var tex: Texture2D = UiKit.load_tex(_TEX_BY_KEY.get(spec.key, ""))
if tex != null:
var tr := TextureRect.new()
tr.texture = tex
tr.stretch_mode = TextureRect.STRETCH_SCALE
node = tr
else:
var ph := ColorRect.new() # 程序化占位(素材缺位=冷检出半同步态,结构在位冒烟安全)
ph.color = Color(1.0, 1.0, 1.0, 0.06)
node = ph
node.name = "Layer_%s" % spec.key
node.position = spec.texture_rect.position
node.size = spec.texture_rect.size
node.mouse_filter = Control.MOUSE_FILTER_IGNORE
return node
func _build_desat_material() -> void:
_desat_mat = ShaderMaterial.new()
var shader := Shader.new()
shader.code = _DESAT_SHADER_SRC
_desat_mat.shader = shader
func _build_outline() -> void:
_outline = Panel.new()
var sb := StyleBoxFlat.new()
sb.draw_center = false
sb.border_color = UiKit.CANDLE
sb.set_border_width_all(2)
sb.set_corner_radius_all(4)
_outline.add_theme_stylebox_override("panel", sb)
_outline.mouse_filter = Control.MOUSE_FILTER_IGNORE
_outline.visible = false
add_child(_outline)
func _build_name_label() -> void:
_name_label = UiKit.label("", UiKit.FS_HINT, false, UiKit.INK_ON_DEEP)
var plate := StyleBoxFlat.new()
plate.bg_color = UiKit.BG_ROOT
plate.set_corner_radius_all(4)
plate.content_margin_left = 8.0
plate.content_margin_right = 8.0
plate.content_margin_top = 2.0
plate.content_margin_bottom = 2.0
_name_label.add_theme_stylebox_override("normal", plate)
_name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE # 名牌不挡命中区
_name_label.visible = false
add_child(_name_label)
func _build_hud_slot(layout: CampSceneLayoutTable) -> void:
_hud_slot = Control.new()
_hud_slot.name = "HudBandSlot"
_hud_slot.set_anchors_preset(Control.PRESET_TOP_WIDE)
_hud_slot.custom_minimum_size = Vector2(0, layout.hud_band_h) # 48px 浮层覆盖,不挤压画面
_hud_slot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_hud_slot)
func _topmost_hotspot_at(pos: Vector2) -> StringName:
if _layout == null:
return &""
var idx: int = _layout.layers.size() - 1 # 逆序=自最上 z 向下
while idx >= 0:
var spec: CampSceneLayerSpec = _layout.layers[idx]
if spec.is_clickable() and _hotspot_rects.get(spec.hotspot_key, Rect2()).has_point(pos):
return spec.hotspot_key
idx -= 1
return &""
func _base_state(key: StringName) -> FeedbackState:
var st: int = _base_states.get(key, FeedbackState.NORMAL)
return FeedbackState.DISABLED if st == FeedbackState.DISABLED else FeedbackState.NORMAL
func _is_disabled(key: StringName) -> bool:
return _base_state(key) == FeedbackState.DISABLED
func _apply_state(key: StringName, state: FeedbackState) -> void:
var layer_key: StringName = _hotspot_layer.get(key, &"")
var node: Control = _layer_nodes.get(layer_key, null)
if node == null:
return
var rect: Rect2 = _hotspot_rects.get(key, Rect2())
var base_y: float = node.position.y - _float_offset_of(key) # 还原到基准位再施新态
_set_float_offset(key, 0)
if _float_tween != null:
_float_tween.kill()
_float_tween = null
match state:
FeedbackState.NORMAL:
node.material = null
_outline.visible = false
_name_label.visible = false
FeedbackState.HOVER:
_apply_hover(key, node, rect)
FeedbackState.PRESSED:
node.position.y = base_y + 1.0 # 按下=位移 1px
FeedbackState.DISABLED:
_apply_disabled(node)
func _apply_hover(key: StringName, node: Control, rect: Rect2) -> void:
if _is_disabled(key):
_apply_disabled(node)
return
node.material = null
_outline.position = rect.position
_outline.size = rect.size
_outline.visible = true
_show_name_plate(key, rect)
_run_float(key) # 悬停浮动(整像素逐级吸附)
func _apply_disabled(node: Control) -> void:
node.material = _desat_mat # 降明度+去饱和(shader;仅锻炉/宝箱会有此态)
_outline.visible = false
_name_label.visible = false
func _run_float(key: StringName) -> void:
_float_tween = create_tween()
for step in 4:
var off := -1 - step
_float_tween.tween_callback(_set_float_offset.bind(key, off)).set_delay(UiKit.motion(0.03))
# 悬离时由 _apply_state(NORMAL) 先归零位——本补间只承载升浮
func _set_float_offset(key: StringName, off: int) -> void:
var layer_key: StringName = _hotspot_layer.get(key, &"")
var node: Control = _layer_nodes.get(layer_key, null)
if node == null or _layout == null:
return
for spec in _layout.layers:
if spec.key == layer_key:
node.position.y = spec.texture_rect.position.y + float(off)
return
func _float_offset_of(key: StringName) -> float:
var layer_key: StringName = _hotspot_layer.get(key, &"")
var node: Control = _layer_nodes.get(layer_key, null)
if node == null or _layout == null:
return 0.0
for spec in _layout.layers:
if spec.key == layer_key:
return node.position.y - spec.texture_rect.position.y
return 0.0
func _show_name_plate(key: StringName, rect: Rect2) -> void:
_name_label.text = String(_HOTSPOT_NAMES.get(key, ""))
_name_label.visible = true
_name_label.reset_size()
var pos := Vector2.ZERO
pos.x = clampf(rect.get_center().x - _name_label.size.x / 2.0, 0.0, _layout.canvas.x - _name_label.size.x)
pos.y = minf(rect.end.y + 8.0, _layout.canvas.y - _name_label.size.y) # 贴底物件防出画(画面内钳制)
_name_label.position = pos
func _on_mouse_exited() -> void:
if _hover_key != &"":
_apply_state(_hover_key, _base_state(_hover_key))
_hover_key = &""
if _press_key != &"":
_apply_state(_press_key, _base_state(_press_key))
_press_key = &""
@@ -0,0 +1 @@
uid://btsrctv5ddx16
@@ -0,0 +1,92 @@
## 营地子界面模态壳基类(锻造工坊/秘宝匣/天赋树/奖励格图鉴四子界面公共形态):
## 实底全屏暖亮档+石纹暗角+顶栏(铭牌+钥石徽)+内容列+底栏返回钮。
## 开合可见性归模态栈(登记层开合一律走栈);返回钮只发 closed——关层编排归 presenter(组合根单口)。
## 余额徽随子类 refresh_camp 刷新(钥石读数=投影单口,禁第二账本)。
class_name CampSubViewBase
extends Control
signal closed
var _balance_label: Label # 钥石徽数值行(探针直读面——ui_probe 余额文案断言改指新界面)
## 壳构建(子类 _init 调用一次):content 由子类先建好传入(列容器/滚动容器皆可)
func _build_shell(title: String, icon_path: String, content: Control) -> void:
# 实底暖亮档(半透 dim 会透出底下场景图——CampOverlay 同案;遮蔽语义由组合根输入锁兜底)
var dim := UiKit._modal_dim(self, UiKit.BG_BRIGHT)
var bd := UiKit.backdrop_tile()
if bd != null:
dim.add_child(bd)
dim.add_child(UiKit.vignette_rect())
var margin := MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 16)
margin.add_theme_constant_override("margin_right", 16)
margin.add_theme_constant_override("margin_top", 12)
margin.add_theme_constant_override("margin_bottom", 12)
dim.add_child(margin)
var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 8)
margin.add_child(col)
_build_top_bar(col, title, icon_path)
content.size_flags_vertical = Control.SIZE_EXPAND_FILL
# 内容米板身(柜台行板对比底=PanelData——旧柜台面板形态延续,行板墨色对比表不脱锚)
var body := PanelContainer.new()
body.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 10, 10, 10)))
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
body.add_child(content)
col.add_child(body)
_build_bottom_bar(col)
visible = false # 登记模态初始隐(开合走栈)
## 顶栏域:铭牌(标题)+钥石徽(余额徽单点——子类 refresh_camp 刷新数值行)
func _build_top_bar(col: VBoxContainer, title: String, icon_path: String) -> void:
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 10)
col.add_child(top)
var plaque := PanelContainer.new()
plaque.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8)))
top.add_child(plaque)
var head := HBoxContainer.new()
head.add_theme_constant_override("separation", 6)
plaque.add_child(head)
if icon_path != "":
var ic := UiKit.icon_rect(icon_path, 32) # 头带图标缺位 null → 纯文本回退
if ic != null:
head.add_child(ic)
head.add_child(UiKit.label(title, UiKit.FS_TITLE, true, UiKit.INK_TITLE))
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(spacer)
var badge := UiKit.keystone_badge("钥石 0") # UiKit 工厂单点(烛光黄底深墨字)
top.add_child(badge)
_balance_label = badge.value_label
## 底栏域:返回营地钮(closed 信号——关层编排归 presenter)
func _build_bottom_bar(col: VBoxContainer) -> void:
var bottom := HBoxContainer.new()
bottom.add_theme_constant_override("separation", 10)
col.add_child(bottom)
var back := UiKit.button("返回营地")
back.pressed.connect(_emit_closed)
bottom.add_child(back)
var spacer2 := Control.new()
spacer2.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bottom.add_child(spacer2)
func open_camp(data: Dictionary) -> void:
refresh_camp(data)
visible = true # 开可见性由栈承载(本行为开层数据投喂的收尾拍)
func refresh_camp(data: Dictionary) -> void:
pass # 子类覆写(余额徽刷新+各自行板/门判据)
func _emit_closed() -> void:
closed.emit()
@@ -0,0 +1 @@
uid://dv0gyjyrtoxkh
+25
View File
@@ -0,0 +1,25 @@
## 奖励格图鉴子界面(自 CampOverlay 图鉴柜台抽出)——占位壳:
## 内容保持挂起(用户令冻结,重启须用户另行发令——卡④ 契约 5),点进看占位、不是禁用态。
## 锁文照录原柜台锁文并随形态订正『柜台』称谓(L2 记录——零新文案)。
class_name CodexView
extends CampSubViewBase
func _init() -> void:
var holder := VBoxContainer.new()
holder.custom_minimum_size = Vector2(0, 336)
var lock_l := UiKit.label(" —— 结构位预留:内容排 Alpha(地图战斗资源差异化批)", UiKit.FS_HINT, false, UiKit.STONE_DEEP)
lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER
lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
lock_l.custom_minimum_size = Vector2(280, 0)
holder.add_child(lock_l)
var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(300, 336)
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.add_child(holder)
_build_shell("奖励格图鉴", UiKit.TEX_ICON_ATLAS, scroll)
func refresh_camp(data: Dictionary) -> void:
_balance_label.text = "钥石 %d" % int(data.get("balance", 0)) # 内容挂起——余额徽随刷(占位态无行板)
@@ -0,0 +1 @@
uid://m1bcfjiceyiq
+21 -2
View File
@@ -1,14 +1,18 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 通用确认弹窗:open(text, confirm_label, cancel_label) 双钮外发 confirmed/cancelled。
## 通用确认弹窗:open(text, confirm_label, cancel_label) 双钮外发 confirmed/cancelled;
## open_three(text, confirm_label, secondary_label, cancel_label) 三钮增设 secondary_confirmed
##(营地大门两分支——卡④ T4;双钮调用方零改动:次钮随 open 隐藏)。
## 文案与按钮语义装配归调用方——本类零业务触碰。
class_name ConfirmDialog
extends Control
signal confirmed
signal secondary_confirmed ## 次确认(三钮形态中钮——营地大门「开始新冒险」出口;卡④ T4)
signal cancelled
var _text: Label
var _confirm_btn: Button
var _secondary_btn: Button
var _cancel_btn: Button
@@ -27,6 +31,9 @@ func _init() -> void:
_cancel_btn = UiKit.button("取消")
_cancel_btn.pressed.connect(_emit_cancelled)
btn_row.add_child(_cancel_btn)
_secondary_btn = UiKit.button("次选项")
_secondary_btn.pressed.connect(_emit_secondary)
btn_row.add_child(_secondary_btn)
_confirm_btn = UiKit.button("确认")
_confirm_btn.pressed.connect(_emit_confirmed)
btn_row.add_child(_confirm_btn)
@@ -37,12 +44,20 @@ func open(text: String, confirm_label: String, cancel_label: String = "取消")
_text.text = text
_confirm_btn.text = confirm_label
_cancel_btn.text = cancel_label
_secondary_btn.visible = false # 双钮形态(次钮隐藏——既有调用方零改动)
# z 序纪律:层序唯一事实源=main.LAYER_CONTRACT(树序与登记序同源派生);开合单口
# 归组合根 ModalFlow(挂起兜底/遮挡记账/开侧同步)——本类只管文案与显示,不自行抬层
visible = true
## 三钮形态(营地大门有断点分支——继续上一段冒险/开始新冒险/取消;Q5 裁甲)
func open_three(text: String, confirm_label: String, secondary_label: String, cancel_label: String) -> void:
open(text, confirm_label, cancel_label)
_secondary_btn.text = secondary_label
_secondary_btn.visible = true
func close() -> void:
visible = false
@@ -55,5 +70,9 @@ func _emit_confirmed() -> void:
confirmed.emit()
func _emit_secondary() -> void:
secondary_confirmed.emit()
func _emit_cancelled() -> void:
cancelled.emit()
+159
View File
@@ -0,0 +1,159 @@
## 锻造工坊子界面(自 CampOverlay 锻造柜台抽出——承载面迁移,界面逻辑复用零重设计):
## 钥匙门(树上 forge_workshop 点亮前锁定态零行板零购买路径;点亮=两区六路线行板)。
## UI 只发意图信号(写口唯一归 MetaProgression 锻造写口族);钮禁用=常态防线(余额/未解锁/满级/已选中)。
class_name ForgeView
extends CampSubViewBase
signal forge_buy_requested(route_id: String) ## 锻造路线购买意图(写口唯一归 MetaProgression.forge_buy_level)
signal forge_unlock_requested(route_id: String) ## 锻造路线解锁意图(写口唯一归 MetaProgression.forge_buy_unlock)
signal forge_select_requested(route_id: String) ## 锻造路线选中意图(写口唯一归 MetaProgression.forge_select)
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
var _forge_lock_l: Label # 锁定态行(钥匙未点亮)
var _forge_workbench: VBoxContainer # 工作间(钥匙点亮后展开——两区六路线行板)
var _forge_zone_weapon: VBoxContainer # 武器锻造区行板容器(三路线)
var _forge_zone_armor: VBoxContainer # 护甲锻造区行板容器(三路线)
var _forge_rows: Array[Dictionary] = [] # [{route_id, name_l, lvl_l, status_l, buy_btn, unlock_btn, select_btn, next_price, unlock_price}](open/refresh 重建;探针消费)
func _init() -> void:
var holder := VBoxContainer.new()
holder.add_theme_constant_override("separation", 6)
holder.custom_minimum_size = Vector2(0, 336)
_forge_lock_l = UiKit.label("🔒 锻造工坊未开放\n\n点亮树上「锻造工坊」钥匙节点后展开工作间。\n(钥匙免费、无前置——树上直接点亮)", UiKit.FS_HINT, false, UiKit.STONE_DEEP)
_forge_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER
_forge_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_forge_lock_l.custom_minimum_size = Vector2(280, 0)
_forge_workbench = VBoxContainer.new()
_forge_workbench.add_theme_constant_override("separation", 6)
_forge_workbench.visible = false # 默认锁定态(钥匙门未判前零行板——门开才建)
_forge_zone_weapon = VBoxContainer.new()
_forge_zone_weapon.add_theme_constant_override("separation", 4)
_forge_zone_armor = VBoxContainer.new()
_forge_zone_armor.add_theme_constant_override("separation", 4)
var wb_wh := UiKit.label("── 武器锻造区 ──", UiKit.FS_HINT, true, UiKit.GOLD_INK)
_forge_workbench.add_child(wb_wh)
_forge_workbench.add_child(_forge_zone_weapon)
var wb_ah := UiKit.label("── 护甲锻造区 ──", UiKit.FS_HINT, true, UiKit.GOLD_INK)
_forge_workbench.add_child(wb_ah)
_forge_workbench.add_child(_forge_zone_armor)
holder.add_child(_forge_lock_l)
holder.add_child(_forge_workbench)
var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(300, 336)
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.add_child(holder)
_build_shell("锻造工坊", UiKit.TEX_ICON_FORGE, scroll)
func refresh_camp(data: Dictionary) -> void:
_balance_label.text = "钥石 %d" % int(data.get("balance", 0))
_refresh_key_gate(data)
_rebuild_forge(data)
# 工作间钥匙门(树上 forge_workshop 行 level 判据——零核心访问,纯视图读投影)
func _refresh_key_gate(data: Dictionary) -> void:
var lvl := 0
for row in (data.get("tree", []) as Array):
if String(row.get("node_id", "")) == "forge_workshop":
lvl = int(row.get("level", 0))
var open := lvl >= 1
_forge_lock_l.visible = not open
_forge_workbench.visible = open
# 锻造行板重建(两区六路线;refresh_camp 调用。行结构仿秘宝匣行板:件名+等级+状态+购买/解锁/选中钮)
func _rebuild_forge(data: Dictionary) -> void:
for zone in [_forge_zone_weapon, _forge_zone_armor]:
for c in zone.get_children():
c.queue_free()
_forge_rows.clear()
if not _forge_workbench.visible:
return # 锁定态(钥匙未点亮):零行板零购买路径——占位纪律延续
var bal := int(data.get("balance", 0))
for row in data.get("forge", []):
var kind := String(row.get("kind", ""))
var zone: VBoxContainer = _forge_zone_weapon if kind == "weapon" else _forge_zone_armor
var card := _forge_route_card(row, bal)
zone.add_child(card["card"])
_forge_rows.append(card)
## 锻造行板构建域:单路线卡(件名+等级+状态+购买/解锁/选中钮——并联双通道呈现面)
func _forge_route_card(row: Dictionary, bal: int) -> Dictionary:
var d := _forge_route_scalars(row)
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 6)
card.add_child(line)
var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(104, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(name_l)
var lvl_l := UiKit.label("Lv%d/%d" % [d["lvl"], d["cap_v"]], 13, false, UiKit.INK_LABEL)
lvl_l.custom_minimum_size = Vector2(44, 0)
line.add_child(lvl_l)
var status_l := UiKit.label(_forge_status_text(d, row), UiKit.FS_HINT, false, UiKit.STONE_DEEP)
status_l.clip_text = true
status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
line.add_child(status_l)
var btns := _forge_route_buttons(d, row, bal, line)
return {"card": card, "route_id": d["rid"], "name_l": name_l, "lvl_l": lvl_l, "status_l": status_l,
"buy_btn": btns["buy_btn"], "unlock_btn": btns["unlock_btn"], "select_btn": btns["select_btn"],
"next_price": d["next_price"], "unlock_price": d["unlock_price"]}
## 锻造行标量束:路线 id/解锁态/初始持有/等级/cap/几何价/解锁价一次取数
func _forge_route_scalars(row: Dictionary) -> Dictionary:
return {
"rid": String(row.get("route_id", "")),
"unlocked": bool(row.get("unlocked", false)),
"initial": bool(row.get("initial_owned", false)),
"lvl": int(row.get("level", 0)),
"cap_v": int(row.get("cap", 0)),
"next_price": int(row.get("next_price", 0)),
"unlock_price": int(row.get("unlock_price", 0)),
}
## 状态文案域(并联双通道:练满前一档 or 一次性解锁价直购;已满级态)
func _forge_status_text(d: Dictionary, row: Dictionary) -> String:
var st_text := "已解锁"
if not d["unlocked"]:
if d["initial"]:
st_text = "未解锁"
else:
st_text = "未解锁 · 练满%s 或 %d 钥石直购" % [String(row.get("prereq_name", "")), d["unlock_price"]]
elif d["lvl"] >= d["cap_v"]:
st_text = "已满级"
return st_text
## 三钮域:购买(已解锁未满级——余额不足禁用)/解锁(待解锁非初始——余额不足禁用)/选中(已解锁——当前=禁用态标记)
func _forge_route_buttons(d: Dictionary, row: Dictionary, bal: int, line: HBoxContainer) -> Dictionary:
var buy_btn: Button = null
if d["unlocked"] and d["lvl"] < d["cap_v"]:
buy_btn = UiKit.button("锻造 %d" % d["next_price"], 0.0) # 行板紧凑钮豁免
buy_btn.pressed.connect(forge_buy_requested.emit.bind(d["rid"]))
buy_btn.disabled = bal < d["next_price"]
line.add_child(buy_btn)
var unlock_btn: Button = null
if not d["unlocked"] and not d["initial"]:
unlock_btn = UiKit.button("解锁 %d" % d["unlock_price"], 0.0) # 行板紧凑钮豁免
unlock_btn.pressed.connect(forge_unlock_requested.emit.bind(d["rid"]))
unlock_btn.disabled = bal < d["unlock_price"]
line.add_child(unlock_btn)
var select_btn: Button = null
if d["unlocked"]:
var sel := bool(row.get("selected", false))
select_btn = UiKit.button("当前" if sel else "选中", 0.0) # 行板紧凑钮豁免
select_btn.pressed.connect(forge_select_requested.emit.bind(d["rid"]))
select_btn.disabled = sel
line.add_child(select_btn)
return {"buy_btn": buy_btn, "unlock_btn": unlock_btn, "select_btn": select_btn}
@@ -0,0 +1 @@
uid://b77xh0e3txmh5
+238
View File
@@ -0,0 +1,238 @@
## 秘宝匣子界面(自 CampOverlay 秘宝匣柜台抽出——承载面迁移,界面逻辑复用零重设计):
## 钥匙门(树上 ks_workshop 点亮前锁定态;点亮=12 件行板+同调装配段+Y 刷新升级行整体迁入)。
## UI 只发意图信号(购买写口唯一归 MetaProgression.buy_treasure_unlock / toggle_attunement / buy_attune_refresh)。
class_name TreasureView
extends CampSubViewBase
signal buy_requested(attr: String) ## 秘宝购买/Y 刷新升级意图(presenter 按 attr 分派)
signal attune_toggled(item_id: String) ## 同调装配切换意图(写口唯一归 MetaProgression.toggle_attunement)
const ROW_PLATE_COL := UiKit.CAMP_ROW_PLATE
var _ws_lock_l: Label # 锁定态行(ks_workshop 未点亮)
var _ws_scroll: ScrollContainer # 秘宝匣内容容器(钥匙点亮后可见)
var _workshop_holder: VBoxContainer # 秘宝匣行板(六行+同调装配段+Y 升级行;探针直读冻结)
var _workshop_rows: Array[Dictionary] = [] # [{item_id, progress_l, status_l, buy_btn, price, unlocked, card}](探针消费)
var _attune_header_l: Label # 同调装配段(Slots 点亮呈现)
var _attune_btns: Array[Dictionary] = [] # [{item_id, btn, unlocked, equipped}]——装配/卸下切换钮(满位禁选随刷)
var _attune_nodes: Array[Control] = [] # 段内节点册(open 重建时定向清理,不扰工坊行板)
var _refresh_l: Label # Y 刷新升级行(双入口之一——树上 ks_refresh 同账)
var _refresh_btn: Button
func _init() -> void:
var holder := VBoxContainer.new()
holder.add_theme_constant_override("separation", 6)
holder.custom_minimum_size = Vector2(0, 336)
_ws_lock_l = UiKit.label("🔒 秘宝匣未开放\n\n点亮树上「秘宝匣」钥匙节点后展开。\n(前置=脊柱生命第 1 级)", UiKit.FS_HINT, false, UiKit.STONE_DEEP)
_ws_lock_l.size_flags_vertical = Control.SIZE_SHRINK_CENTER
_ws_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_ws_lock_l.custom_minimum_size = Vector2(280, 0)
_ws_scroll = ScrollContainer.new()
_ws_scroll.custom_minimum_size = Vector2(300, 336)
_ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
_workshop_holder = VBoxContainer.new()
_workshop_holder.add_theme_constant_override("separation", 6)
_ws_scroll.add_child(_workshop_holder)
holder.add_child(_ws_lock_l)
holder.add_child(_ws_scroll)
var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(300, 336)
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.add_child(holder)
_build_shell("秘宝匣", UiKit.TEX_ICON_WORKSHOP, scroll)
func open_camp(data: Dictionary) -> void:
_rebuild_workshop(data) # 行板+同调装配段+Y 尾行随开层全量重建(段尾链 _rebuild_attune)
super.open_camp(data)
func refresh_camp(data: Dictionary) -> void:
var balance := int(data.get("balance", 0))
_balance_label.text = "钥石 %d" % balance
_refresh_key_gate(data)
_refresh_workshop_rows(data, balance)
_refresh_attune_segment(data, balance)
# 秘宝匣钥匙门(树上 ks_workshop 行 level 判据——零核心访问,纯视图读投影)
func _refresh_key_gate(data: Dictionary) -> void:
var lvl := 0
for row in (data.get("tree", []) as Array):
if String(row.get("node_id", "")) == "ks_workshop":
lvl = int(row.get("level", 0))
var open := lvl >= 1
_ws_lock_l.visible = not open
_ws_scroll.visible = open
func _rebuild_workshop(data: Dictionary) -> void:
_workshop_rows.clear()
for child in _workshop_holder.get_children():
child.queue_free()
_attune_nodes.clear()
_attune_btns.clear()
_attune_header_l = null
_refresh_l = null
_refresh_btn = null
for row in data.get("workshop", []):
var card := _workshop_card(row)
_workshop_holder.add_child(card["card"])
_workshop_rows.append(card)
_rebuild_attune(data)
## 秘宝匣行板构建域:件名(分档后缀+效果摘要悬停)+进度列+状态列+钥石购买钮(购买件未解锁在列)
func _workshop_card(row: Dictionary) -> Dictionary:
var card := PanelContainer.new()
card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4)))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 12)
card.add_child(line)
var tier_name := String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), ""))
var name_l := UiKit.label(String(row.get("display_name", "")) + ("·" + tier_name if tier_name != "" else ""), UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(132, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
name_l.tooltip_text = String(row.get("desc", "")) # 件效果摘要悬停可见(分档呈现契约——槽位档位=名后缀)
line.add_child(name_l)
var progress_l := UiKit.label("", UiKit.FS_HINT, false, UiKit.STONE_DEEP)
progress_l.custom_minimum_size = Vector2(150, 0)
progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜
progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(progress_l)
# 状态色用亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式
var status_l := UiKit.label("", UiKit.FS_HINT, true, UiKit.STONE_DEEP)
status_l.clip_text = true # 「已解锁 · 能力封存中」不撑柜(余宽吸收者)
status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
line.add_child(status_l)
# 钥石购买钮(购买件且未解锁时在列;写口唯一=buy_treasure_unlock,
# 经 buy_requested(item_id) 信号走 presenter 分支;禁用态=余额不足)
var buy_btn: Button = null
if not bool(row.get("unlocked", false)) and String(row.get("mode", "")) == "keystone":
buy_btn = UiKit.button("购买 %d" % int(row.get("price", 0)), 0.0) # 行板紧凑钮豁免
buy_btn.pressed.connect(_emit_buy_requested.bind(String(row.get("item_id", ""))))
line.add_child(buy_btn)
return {"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l, "buy_btn": buy_btn, "price": int(row.get("price", 0)), "unlocked": bool(row.get("unlocked", false)), "card": card}
# 同调装配段重建(秘宝匣尾部段:Slots 头行+九件行板+Y 升级尾行)
func _rebuild_attune(data: Dictionary) -> void:
var att: Dictionary[Variant, Variant] = data.get("attune", {})
_attune_header_l = UiKit.label("", UiKit.FS_HINT, true, UiKit.INK)
_workshop_holder.add_child(_attune_header_l)
_attune_nodes.append(_attune_header_l)
for row in att.get("items", []):
var line := _attune_item_line(row)
_workshop_holder.add_child(line)
_attune_nodes.append(line)
_refresh_attune_tail()
## 同调件行板构建域:件名+形态+档位+装配切换钮(未解锁禁装——toggle 写口同守卫)
func _attune_item_line(row: Dictionary) -> HBoxContainer:
var iid := String(row.get("item_id", ""))
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 6)
var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK)
name_l.custom_minimum_size = Vector2(96, 0)
name_l.clip_text = true
name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
line.add_child(name_l)
var form_l := UiKit.label("被动" if String(row.get("form", "")) == "passive" else "主动·充能", UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
form_l.custom_minimum_size = Vector2(58, 0)
line.add_child(form_l)
# 档位标签(槽位档位显示:件档=普通/优秀/稀有,装入须件档≤格档契约 2 的呈现半边)
var tier_l := UiKit.label(String(UiKit.TREASURE_TIER_LABELS.get(String(row.get("tier", "")), "—")), UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
tier_l.custom_minimum_size = Vector2(36, 0)
line.add_child(tier_l)
var btn := UiKit.button("装配", 0.0) # 行板紧凑钮豁免
btn.pressed.connect(attune_toggled.emit.bind(iid))
btn.disabled = not bool(row.get("unlocked", false)) # 未解锁禁装(解锁为装配前置——toggle 写口同守卫)
line.add_child(btn)
_attune_btns.append({"item_id": iid, "btn": btn, "unlocked": bool(row.get("unlocked", false)), "equipped": bool(row.get("equipped", false))})
return line
## Y 升级尾行构建域(双入口之一——树上 ks_refresh 同账)
func _refresh_attune_tail() -> void:
var refresh_line := HBoxContainer.new()
refresh_line.add_theme_constant_override("separation", 6)
_refresh_l = UiKit.label("", UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)
refresh_line.add_child(_refresh_l)
_refresh_btn = UiKit.button("升级", 0.0) # 行板紧凑钮豁免
_refresh_btn.pressed.connect(_emit_buy_requested.bind("attune_refresh"))
refresh_line.add_child(_refresh_btn)
_workshop_holder.add_child(refresh_line)
_attune_nodes.append(refresh_line)
## 秘宝匣行板刷新域:进度列(解锁混合制三形态)与状态色(亮板双色墨制)与购买钮余额联动
func _refresh_workshop_rows(data: Dictionary, balance: int) -> void:
var ws_by_id := {}
for row in data.get("workshop", []):
ws_by_id[String(row.get("item_id", ""))] = row
for card in _workshop_rows:
var row: Dictionary[Variant, Variant] = ws_by_id.get(String(card.item_id), {})
if row.is_empty():
continue
var unlocked := bool(row.get("unlocked", false))
card.progress_l.text = _workshop_progress_text(row, unlocked)
var st_text := "未解锁"
var st_col: Color = UiKit.STONE_DEEP
if unlocked:
st_text = "已解锁"
st_col = UiKit.GOLD_INK # GOLD_DEEP 对行板 3.00:1 越带,换深金墨 5.84:1
card.status_l.text = st_text
card.status_l.add_theme_color_override("font_color", st_col)
card.status_l.tooltip_text = st_text # 截断兜底(状态列 EXPAND_FILL 吸余宽——悬停看全文)
# 购买钮余额联动(禁用态=余额不足,常态防线;pattern 1 兜底拒绝可见归 presenter)
if card.get("buy_btn") != null:
card.buy_btn.disabled = balance < int(card.get("price", 0))
## 进度列文案域(解锁混合制三形态:钥石购买价/条件进度拼接/已入手)
func _workshop_progress_text(row: Dictionary, unlocked: bool) -> String:
if unlocked:
return "已入手"
var mode := String(row.get("mode", ""))
if mode == "keystone":
return "钥石 %d" % int(row.get("price", 0))
if mode == "condition":
var parts: Array[String] = []
for req_v in (row.get("reqs", []) as Array):
var req: Dictionary[Variant, Variant] = req_v as Dictionary
var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件"))
parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))])
return "、".join(parts)
return ""
## 同调装配段刷新域(Slots 点亮/满位禁选/Y 升级行余额联动——就地刷)
func _refresh_attune_segment(data: Dictionary, balance: int) -> void:
var att: Dictionary[Variant, Variant] = data.get("attune", {})
if _attune_header_l != null:
var slots_n := int(att.get("slots", 0))
var eq: Array[Variant] = att.get("equipped", [])
_attune_header_l.text = "── 同调装配 Slots %d/%d ──" % [eq.size(), slots_n]
var eq_set := {}
for v in eq:
eq_set[String(v)] = true
for card2 in _attune_btns:
var on: bool = eq_set.has(String(card2.item_id))
card2.btn.text = "卸下" if on else "装配"
# 满位禁选+未解锁禁装(已装件恒可卸下;unlocked 快照随 rebuild 刷新)
card2.btn.disabled = (not on and eq.size() >= slots_n) or (not on and not bool(card2.get("unlocked", false)))
if _refresh_l != null:
var rf: Dictionary[Variant, Variant] = att.get("refresh", {})
_refresh_l.text = "三选一刷新 +%d 次/冒险 · Lv%d" % [int(rf.get("charges", 0)), int(rf.get("level", 0))]
_refresh_btn.text = "已满级" if bool(rf.get("maxed", false)) else "升级 %d 钥石" % int(rf.get("price", 0))
_refresh_btn.disabled = bool(rf.get("maxed", false)) or balance < int(rf.get("price", 0))
func _emit_buy_requested(attr: String) -> void:
buy_requested.emit(attr)
@@ -0,0 +1 @@
uid://dcakccwlacegq
+105
View File
@@ -0,0 +1,105 @@
## 天赋树子界面(自 CampOverlay 树淬炼柜台抽出——承载面迁移,界面逻辑复用零重设计):
## 树脊柱界面形态=主界面(脉分组行板:节点名/等级/价/购买钮+Fork 双态切换钮段)。
## UI 只发意图信号(写口唯一归 MetaProgression.tree_buy/tree_buy_fork/tree_toggle_fork)。
class_name TreeView
extends CampSubViewBase
signal tree_buy_requested(node_id: String) ## 天赋树购买意图(写口唯一归 MetaProgression.tree_buy)
signal tree_fork_toggled(group: String, side: String) ## 双态切换意图(一节点双态——零币切态)
var _tree_holder: VBoxContainer # 树淬炼内容(脊柱两行+钥匙/socket 行板+Fork 双态钮段)
var _tree_buy_btns: Array[Dictionary] = [] # [{node_id, btn}]——树购买/解锁钮登记面(open/refresh 重建时刷新;探针消费)
var _tree_fork_btns: Array[Dictionary] = [] # [{group, side, btn}]——Fork 双态切换钮登记面(探针消费)
func _init() -> void:
_tree_holder = VBoxContainer.new()
_tree_holder.add_theme_constant_override("separation", 6)
var tree_scroll := ScrollContainer.new()
tree_scroll.custom_minimum_size = Vector2(300, 336)
tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
tree_scroll.add_child(_tree_holder)
var content := VBoxContainer.new()
content.add_child(tree_scroll)
_build_shell("天赋树 · 淬炼", "", content)
func refresh_camp(data: Dictionary) -> void:
_balance_label.text = "钥石 %d" % int(data.get("balance", 0))
_rebuild_tree(data)
# 天赋树段重建(脉分组行板:节点名/等级/价/购买钮 + Fork 双态切换钮;
# 树数据 = 组合根 _camp_data().tree 投影 [{node_id, display_name, vein, kind, level, price, can_buy,
# currency}...] + fork_groups [{group, node_id, active_side, owned, can_buy, price, sides:[{side, display_name, effects}]}])
func _rebuild_tree(data: Dictionary) -> void:
for c in _tree_holder.get_children():
c.queue_free()
_tree_buy_btns.clear()
_tree_fork_btns.clear()
var tree: Array[Variant] = data.get("tree", []) as Array
var forks: Array[Variant] = data.get("fork_groups", []) as Array
var vein_names := {"spine": "·脊柱淬炼·", "forge": "·锻造脉·", "keystone": "·钥石脉·", "attune": "·同调脉·"}
var last_vein := ""
for row in tree:
var vein := String(row.get("vein", ""))
if vein != last_vein:
last_vein = vein
var vh := UiKit.label(String(vein_names.get(vein, vein)), UiKit.FS_HINT, true, UiKit.GOLD_INK) # GOLD_DEEP 对柜台板 2.67:1 越带,换深金墨 5.21:1
_tree_holder.add_child(vh)
if String(row.get("kind", "")) == "fork":
continue # Fork 行去行板化——由 fork_groups 段承载双态切换钮
_tree_holder.add_child(_tree_row_line(row))
for fg in forks:
_tree_holder.add_child(_tree_fork_line(fg))
## 树行板构建域:节点名(含门槛标记)+购买钮(追加序与锚句随段整体搬迁)
func _tree_row_line(row: Dictionary) -> HBoxContainer:
var hb := HBoxContainer.new()
hb.add_theme_constant_override("separation", 6)
var lvl := int(row.get("level", 0))
var cap := int(row.get("cap", 1))
var name_l := UiKit.label("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, cap], 13, false, UiKit.INK_LABEL)
name_l.custom_minimum_size = Vector2(150, 0)
name_l.clip_text = true
var gate := String(row.get("gate_text", ""))
if gate != "":
name_l.text += " 🔒" # 等级阈值前置未达标标记(R1 机制位——悬停看门槛;达标后随 refresh 消失)
name_l.tooltip_text = "等级门槛:%s" % gate
hb.add_child(name_l)
var btn := UiKit.button("购买 %d 钥石" % int(row.get("price", 0)), 0.0) # 柜台行板紧凑钮豁免宽度基准
btn.pressed.connect(func(): tree_buy_requested.emit(String(row.get("node_id", ""))))
btn.disabled = not bool(row.get("can_buy", false))
hb.add_child(btn)
_tree_buy_btns.append({"node_id": String(row.get("node_id", "")), "btn": btn})
return hb
## Fork 双态行板构建域:组状态文案+解锁钮或双态切换钮
func _tree_fork_line(fg: Dictionary) -> HBoxContainer:
var group := String(fg.get("group", ""))
var active := String(fg.get("active_side", ""))
var owned := bool(fg.get("owned", false))
var fb := HBoxContainer.new()
fb.add_theme_constant_override("separation", 6)
var state_txt := "未解锁"
if owned:
state_txt = "激活:%s" % active
var gl := UiKit.label("%s[%s]" % [String(fg.get("display_name", group)), state_txt], 13, false, UiKit.INK_LABEL)
fb.add_child(gl)
if not owned:
var bb := UiKit.button("解锁 %d 钥石" % int(fg.get("price", 0)), 0.0) # 行板紧凑钮豁免
bb.pressed.connect(func(): tree_buy_requested.emit(String(fg.get("node_id", ""))))
bb.disabled = not bool(fg.get("can_buy", false))
fb.add_child(bb)
_tree_buy_btns.append({"node_id": String(fg.get("node_id", "")), "btn": bb})
else:
for side_row in (fg.get("sides", []) as Array):
var sd := String(side_row.get("side", ""))
var sb := UiKit.button(String(side_row.get("display_name", sd)), 0.0) # 行板紧凑钮豁免
sb.pressed.connect(func(): tree_fork_toggled.emit(group, sd))
fb.add_child(sb)
_tree_fork_btns.append({"group": group, "side": sd, "btn": sb})
return fb
+1
View File
@@ -0,0 +1 @@
uid://mjjrdelhi7yg
+13
View File
@@ -247,6 +247,19 @@ const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_
const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王
# 近景地台独立件(§6.1——源 48×12 九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定不吃 wall_ambient tint)
const TEX_STAGE_PLAZA := "res://assets/art/scene/scene_stage_plaza.png"
# 营地场景十层(art-bible §6.2——z0 bg 不透明全幅+z1 围墙透明细条+八物件透明层;布局矩形/命中矩形/z 序
# 唯一事实源 = data/tables/camp_scene_layout.tres;素材名跟本组常量走(先定常量防断链);
# 缺位 load_tex→null→程序化占位,冒烟安全)
const TEX_CAMP_BG := "res://assets/art/scene/scene_camp_bg.png" # z0 背景层(不透明全幅 1280×800)
const TEX_CAMP_WALL := "res://assets/art/scene/scene_camp_wall.png" # z1 围墙层(透明细条,门洞留缺口)
const TEX_CAMP_BOARD := "res://assets/art/scene/scene_camp_board.png" # z2 布告板
const TEX_CAMP_GATE := "res://assets/art/scene/scene_camp_gate.png" # z3 门扇
const TEX_CAMP_DUMMY := "res://assets/art/scene/scene_camp_dummy.png" # z4 木桩
const TEX_CAMP_FORGE := "res://assets/art/scene/scene_camp_forge.png" # z4 锻炉铁砧
const TEX_CAMP_TENT := "res://assets/art/scene/scene_camp_tent.png" # z4 帐篷(承载件不可点)
const TEX_CAMP_CHEST := "res://assets/art/scene/scene_camp_chest.png" # z5 宝箱
const TEX_CAMP_NOTEBOOK := "res://assets/art/scene/scene_camp_notebook.png" # z5 笔记本
const TEX_CAMP_CAMPFIRE := "res://assets/art/scene/scene_camp_campfire.png" # z6 篝火(命中只取下半火堆)
# 护甲类型图标三键(art-bible §5.3 工单:对象键 = 甲档变体;源 24×24 显示 24px = 1× 甲类整数倍;
# 缺位 load_tex→null→字形回退,冒烟安全)
const TEX_ARMOR_LIGHT := "res://assets/ui/icons/ui_armor_light.png"
+31 -6
View File
@@ -6,15 +6,16 @@
extends Node
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 隔离样板单点
const BoardViewScript := preload("res://presentation/views/board_view.gd") # 算式单点静态调用载体(迭代 46 卡C)
# 分辨率档位表(逻辑视口尺寸;数据侧——增删档位只改这里,断言代码零改动)
const VIEWPORTS := [
{"label": "基座 1280x800", "size": Vector2i(1280, 800)},
{"label": "16:9 全屏 1920x1080", "size": Vector2i(1920, 1080)},
]
const MIN_CELL := 36.0 # 格子可读门槛(battle.md §棋盘格尺寸公式 1280 档 12×12 → 36px)
var _problems: PackedStringArray = []
var _cell_reads: Dictionary = {} # 逐档格边长实读留痕(label→cell_size;PASS 行引基座值)
var _est_reads: Dictionary = {} # 逐档 13/14 板推定读数留痕(label→{13,14,avail};PASS 后打印对拍)
func _ready() -> void:
@@ -41,8 +42,12 @@ func _ready() -> void:
# 复原本项目基座(探针不该把进程留在末档尺寸)
get_tree().root.content_scale_size = Vector2i(1280, 800)
if _problems.is_empty():
print("[layout_probe] PASS — %d 档几何不变量全过(三区非零/不越视口/互不重叠/view 含于 D 区/格子 ≥%.0fpx)"
% [VIEWPORTS.size(), MIN_CELL])
print("[layout_probe] PASS — %d 档几何不变量全过+格边长等价成立(基座实读 %.0fpx)〔格边长 V1〕"
% [VIEWPORTS.size(), float(_cell_reads.get("基座 1280x800", -1.0))])
for label in _est_reads:
var e: Dictionary = _est_reads[label]
print("[layout_probe] [%s] 13 板推定 %dpx/14 板推定 %dpx(实板可用面积 %s)〔格边长 V4〕"
% [label, int(e["13"]), int(e["14"]), str(e["avail"])])
get_tree().quit(0)
else:
for p in _problems:
@@ -91,5 +96,25 @@ func _check_one(label: String, main: Node) -> void:
_problems.append("[%s] 顶层区重叠 %s×%s(%s ∩ %s)"
% [label, keys[i], keys[j], rects[keys[i]], rects[keys[j]]])
var cell: float = main.ui.battle.board_zone.board_view.get("cell_size")
if cell < MIN_CELL:
_problems.append("[%s] 格子边长 %.1fpx < 可读门槛 %.0fpx" % [label, cell, MIN_CELL])
_cell_reads[label] = cell
# 格边长接线守卫——判据=等价(生产写入值与单点算式严格相等,生产接线断开即红;
# 全仓禁任何人造格边长可读阈值)。
# 板型 N 无公开出口→`bv.get("_board")` 内部字段直查=受控例外(改动—验收对照表行 1 注明理由)。
var b = bv.get("_board")
if b == null:
_problems.append("[%s] _board 为 null(板型 N 无从取值,等价断言无依据)〔格边长 V1〕" % label)
elif cell != float(BoardViewScript._cell_for(maxi(int(b.width), int(b.height)), bv.size)):
_problems.append("[%s] 格边长等价破——生产写入 %.1fpx ≠ _cell_for(n=%d, avail=%s) 返回 %.1fpx(实板可用面积 %s)〔格边长 V1〕"
% [label, cell, maxi(int(b.width), int(b.height)), str(bv.size),
float(BoardViewScript._cell_for(maxi(int(b.width), int(b.height)), bv.size)), str(bv.size)])
# 最大板推定留痕(迭代 46 卡C T2——实板可用面积代入 13/14 板型求算式单点,读数打印不作判红:
# 锚点=用户验收的现行渲染值;对拍归实现报告,对拍不一致=卡 §9 L3 #2 呈报,禁实现侧改锚;
# 零尺寸前置=可用面积不可信时推定无依据)
if bv.size.x <= 0.0 or bv.size.y <= 0.0:
_problems.append("[%s] board_view 零尺寸(可用面积不可信,格边长推定无依据)〔格边长 V4〕" % label)
else:
_est_reads[label] = {
"13": BoardViewScript._cell_for(13, bv.size),
"14": BoardViewScript._cell_for(14, bv.size),
"avail": str(bv.size),
}
+76 -6
View File
@@ -27,6 +27,7 @@ func _ready() -> void:
await _step_boot_menu()
await _step_menu_to_camp()
await _step_camp_back_to_menu()
await _step_camp_esc_and_hud() # 卡⑤——Esc 四态路由/三出口/设置语境分支/HUD 读数
await _step_save_select_open_back() # 迭代 33 卡③ sl10——选档开合+返回路径+ESC 语境不响应
await _step_menu_to_adventure()
await _step_level_to_battle_and_back()
@@ -51,7 +52,7 @@ func _ready() -> void:
func _screen_seals() -> Dictionary:
return {
"菜单": main.ui.screens.start_overlay != null and main.ui.screens.start_overlay.visible,
"营地": main.ui.modals.camp_overlay != null and main.ui.modals.camp_overlay.visible,
"营地": main.ui.modals.camp.scene != null and main.ui.modals.camp.scene.visible,
"冒险": main.run_scope != null and main.run_scope.is_adventure,
"终局": main.ui.modals.end_overlay != null and main.ui.modals.end_overlay.visible,
}
@@ -115,14 +116,14 @@ func _step_menu_to_camp() -> void:
_expect_exclusive("②菜单→营地", "营地")
# ---- ③ 营地 → 菜单(回退路径:营地关闭发自覆盖层自身信号)----
# ---- ③ 营地 → 菜单(回退路径:篝火二次确认动线——卡④ 营地唯一回菜单出口;选档层随关层收闭同收)----
func _step_camp_back_to_menu() -> void:
if main.ui.modals.camp_overlay == null:
_problems.append("③营地→菜单:营地覆盖层句柄缺失")
if main.ui.modals.camp.scene == null:
_problems.append("③营地→菜单:营地场景根句柄缺失〔句柄子容器批 V1〕")
return
main.ui.modals.camp_overlay.closed.emit()
main.ui.modals.camp.scene.object_pressed.emit(&"campfire")
await _frames()
main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点)
main.ui.modals.confirm_dialog.confirmed.emit() # 二次确认离场(回主菜单——卡④ T4)
await _frames()
_expect_exclusive("③营地→菜单(回退)", "菜单")
@@ -229,3 +230,72 @@ func _step_end_back_to_menu() -> void:
_expect_exclusive("⑦终局→菜单", "菜单")
if main.run_scope != null and main.run_scope.is_adventure:
_problems.append("⑦终局→菜单:冒险态未收束(run_scope 仍标 is_adventure)")
# ---- ③' 营地 Esc 浮窗+HUD(卡⑤——真实键盘合成路径 push_input;四态路由+三出口+语境分支+余额单口)----
func _step_camp_esc_and_hud() -> void:
ProbeUiUtil.isolate_save(main, "smoke_screen_flow_esc", false, false, true)
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_esc"):
_problems.append("[esc] 营地入口缺失(选档层新档钮驱动中断)〔ESC批 V1〕")
return
await _frames()
_push_esc()
await _frames()
if not main.ui.modals.camp.esc_overlay.visible:
_problems.append("[esc] 营地根态 Esc 未唤起浮窗(契约 9③)〔ESC批 V1〕")
_push_esc()
await _frames()
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("[esc] 再按 Esc 未关浮窗(Q2 同键单义切换失守)〔ESC批 V1〕")
_push_esc(true) # echo 连发(真实键盘长按形态)
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") # 子界面开态
await _frames()
_push_esc()
await _frames()
if main.ui.modals.camp.esc_overlay.visible or not main.ui.modals.camp.tree.visible:
_problems.append("[esc] 子界面开态 Esc 反馈(契约 9① 无反馈失守)〔ESC批 V1〕")
main.ui.modals.camp.tree.closed.emit()
await _frames()
# HUD 余额(契约 5 时机③——夹具直写后显式触发才变;禁 band 自刷假设)
main.meta.meta_state._add_keystone(9999)
main._camp_pres.views.refresh_bus.refresh(main._camp_data()) # 显式触发刷新单口
var band: Control = main.ui.modals.camp.scene.get_node("HudBandSlot").get_child(0)
if not String(band._badge.value_label.text).begins_with("钥石 9999"):
_problems.append("[esc] HUD 余额读数≠存档单口现值(直写+显式触发未变)〔ESC批 V6〕")
# 设置开在浮窗之上→Esc 无反馈且浮窗不误关(契约 9①);关设置回浮窗不回主菜单(契约 3 语境分支)
_push_esc()
await _frames()
main._on_esc_settings()
await _frames()
if not main.ui.modals.settings_overlay.visible:
_problems.append("[esc] 浮窗设置出口断裂〔ESC批 V4〕")
_push_esc()
await _frames()
if not main.ui.modals.camp.esc_overlay.visible:
_problems.append("[esc] 设置开态 Esc 误关浮窗(契约 9①——浮窗不隔层关闭)〔ESC批 V1〕")
main._on_settings_closed()
await _frames()
if not main.ui.modals.camp.esc_overlay.visible or main.ui.screens.start_overlay.visible:
_problems.append("[esc] 关设置后未回浮窗或回弹主菜单(契约 3 语境分支失守)〔ESC批 V4〕")
# 返回主菜单动线(契约 4——浮窗自收+营地隐+选档收+菜单显)
main.ui.modals.camp.esc_overlay.return_requested.emit()
await _frames()
if main.ui.modals.camp.esc_overlay.visible or main.ui.modals.camp.scene.visible or main.ui.modals.save_select_overlay.visible or not main.ui.screens.start_overlay.visible:
_problems.append("[esc] 返回主菜单动线失守(浮窗自收/营地隐/选档收/菜单显)〔ESC批 V3〕")
# 非营地语境(boot 菜单)Esc→无响应(契约 9④ 回归面)
_push_esc()
await _frames()
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("[esc] 非营地语境 Esc 唤起浮窗(契约 9④ 失守)〔ESC批 V1〕")
func _push_esc(echo := false) -> void:
var ev := InputEventKey.new()
ev.keycode = KEY_ESCAPE
ev.physical_keycode = KEY_ESCAPE
ev.pressed = true
ev.echo = echo
main.get_viewport().push_input(ev, true) # 真实键盘合成路径(:153 先例——push_input 不更新 Input 单例动作态)
+6 -8
View File
@@ -53,7 +53,7 @@ func _ready() -> void:
var top_ry := -1
for c: Node in [main.ui.screens.start_overlay, main.ui.screens.battle_root, main.ui.screens.inter_overlay,
main.ui.modals.legend_overlay, main.ui.modals.node_overlay, main.ui.modals.level_screen, main.ui.modals.curio_overlay,
main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp_overlay, main.ui.modals.settings_overlay,
main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.settings_overlay,
main.ui.modals.status_panel, main.ui.modals.career_overlay, main.ui.modals.confirm_dialog, main.ui.modals.debug_overlay]:
if c == null or not (c is CanvasItem) or not (c as CanvasItem).visible:
continue
@@ -119,17 +119,15 @@ func _ready() -> void:
await get_tree().process_frame
if main.ui.modals.career_overlay.visible or not main.ui.screens.start_overlay.visible:
_problems.append("生涯页关闭后页未隐藏或开始层未恢复")
# ② 营地(进档开层新路径——卡② mn9;槽模板注入隔离命名空间)
# ② 营地场景图(进档开层新路径——卡② mn9;截图=可点击场景图形态——卡④ 契约 9 V1 验收载体)
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_shot_probe_camp"):
_problems.append("营地入口缺失(选档层新档钮——新路径驱动中断)")
_problems.append("营地入口缺失(选档层新档钮——新路径驱动中断)〔句柄子容器批 V1〕")
else:
await get_tree().process_frame
await _snap("camp")
var close_btn: Button = ProbeUiUtil.find_button(main.ui.modals.camp_overlay, "关闭营地", "contains")
if close_btn != null:
close_btn.pressed.emit()
await get_tree().process_frame
main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态)
main.ui.modals.camp.scene.object_pressed.emit(&"campfire") # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5)
await get_tree().process_frame
main.ui.modals.confirm_dialog.confirmed.emit() # 二次确认离场(关层收闭——选档层随收)
await get_tree().process_frame
# ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10)
main._start_adventure_from_menu()
+106 -53
View File
@@ -62,123 +62,131 @@ func _ready() -> void:
# 原 swap=false 形态的写盘走 iso 实例旧路径,与槽文件断言不同源——卡② mn9 换代)
var iso_c = ProbeUiUtil.isolate_save(main, "smoke_click_probe_camp", false, false, true)
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_click_probe_camp"):
_problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)")
_problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)〔句柄子容器批 V1〕")
else:
await get_tree().process_frame
if not main.ui.modals.camp_overlay.visible:
_problems.append("营地层未打开(进档开层新路径)")
elif main.ui.modals.camp_overlay._tree_buy_btns.is_empty():
_problems.append("树淬炼柜台购买钮缺失(点击路径——四柜台重排后树=主界面)")
if not main.ui.modals.camp.scene.visible:
_problems.append("营地场景根未打开(进档开层新路径)〔句柄子容器批 V1〕")
main.ui.modals.camp.scene.object_pressed.emit(&"dummy") # 场景点选真实路由(木桩→天赋树子界面——卡④ T5)
await get_tree().process_frame
if main.ui.modals.camp.tree._tree_buy_btns.is_empty():
_problems.append("树淬炼购买钮缺失(四柜台拆解后树=独立子界面)〔句柄子容器批 V1〕")
else:
# 树钮句柄用后即取(refresh_camp 随刷重建树行板——旧钮 queue_free,跨刷新句柄必失效)
iso_c.meta_state._add_keystone(9999)
main.ui.modals.camp_overlay.refresh_camp(main._camp_data())
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
var buy_btn: Button = null
for card in main.ui.modals.camp_overlay._tree_buy_btns:
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "sp_hp":
buy_btn = card.btn
if buy_btn == null:
_problems.append("脊柱生命行缺失(点击路径)")
_problems.append("脊柱生命行缺失(点击路径)〔句柄子容器批 V1〕")
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(写调用唯一归组合根)
await get_tree().process_frame
if int(iso_c.meta_state.keystone_balance()) != bal_before - price:
_problems.append("营地树购买未出账(%d→%d 应 −%d)" % [bal_before, iso_c.meta_state.keystone_balance(), price])
_problems.append("营地树购买未出账(%d→%d 应 −%d)〔句柄子容器批 V1〕" % [bal_before, iso_c.meta_state.keystone_balance(), price])
var iso_chk = load("res://core/save_manager.gd").new("user://smoke_click_probe_camp_slot_1.json")
iso_chk.load_from_disk()
if int(iso_chk.meta_state.tree_state().nodes.get("sp_hp", 0)) != 1:
_problems.append("营地树购买未持久(reload 后级 != 1)")
_problems.append("营地树购买未持久(reload 后级 != 1)〔句柄子容器批 V1〕")
# Fork 双态切换点击路径(V20——购=解锁默认 A,钮切 B;前置=同调位 1,先点亮)
var socket_btn: Button = null
for card in main.ui.modals.camp_overlay._tree_buy_btns:
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "att_socket1":
socket_btn = card.btn
if socket_btn != null:
socket_btn.pressed.emit()
await get_tree().process_frame
main.ui.modals.camp_overlay.refresh_camp(main._camp_data())
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
var fork_btn: Button = null
for card in main.ui.modals.camp_overlay._tree_buy_btns:
for card in main.ui.modals.camp.tree._tree_buy_btns:
if String(card.node_id) == "fork_posture":
fork_btn = card.btn
if fork_btn == null:
_problems.append("Fork 解锁钮缺失(点击路径)")
_problems.append("Fork 解锁钮缺失(点击路径)〔句柄子容器批 V1〕")
else:
fork_btn.pressed.emit()
await get_tree().process_frame
main.ui.modals.camp_overlay.refresh_camp(main._camp_data())
main.ui.modals.camp.tree.refresh_camp(main._camp_data())
await get_tree().process_frame
var side_b: Button = null
for fk in main.ui.modals.camp_overlay._tree_fork_btns:
for fk in main.ui.modals.camp.tree._tree_fork_btns:
if String(fk.group) == "fg_posture" and String(fk.side) == "b":
side_b = fk.btn
if side_b == null:
_problems.append("Fork 双态切换钮缺失(解锁后应展开 a/b 钮)")
_problems.append("Fork 双态切换钮缺失(解锁后应展开 a/b 钮)〔句柄子容器批 V1〕")
else:
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()
if String(iso_fk.meta_state.tree_state().fork_sides.get("fg_posture", "")) != "b":
_problems.append("Fork 切态未持久(reload 后激活态 != b)")
# ---- 营地六处灰字取色断言(迭代36 wc4——真实入口开营地后按文本锚取 label,
# font_color 逐处 == STONE_DEEP;六源位 :167 图鉴锁文/:417+:476 行状态字/:509 同调形态/:513 档位标签/:525 刷新行)----
var camp_labels: Array = []
_collect_labels_by_text(main.ui.modals.camp_overlay, camp_labels)
var atlas_locks := _labels_with_text(camp_labels, "柜台结构位预留")
if atlas_locks.is_empty():
_problems.append("营地灰字:图鉴柜台锁文 label 未找到(wc4 定位锚失守)")
for l in atlas_locks:
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:图鉴锁文未用 STONE_DEEP(wc4 :167)")
# 「未解锁」锚收窄到秘宝匣行容器子树(树行/锻造行也有「未解锁」态字样——非 wc4 目标位)
var locked_status: Array = []
if main.ui.modals.camp_overlay.get("_workshop_holder") != null:
var ws_labels: Array = []
_collect_labels_by_text(main.ui.modals.camp_overlay._workshop_holder, ws_labels)
locked_status = _labels_with_text(ws_labels, "未解锁")
_problems.append("Fork 切态未持久(reload 后激活态 != b)〔句柄子容器批 V1〕")
# ---- 营地灰字取色断言(迭代36 wc4——四子界面拆分后按视图分扫;卡④ T5 迁移):
# 图鉴锁文→codex 视图(锚随形态订正去『柜台』);秘宝行未解锁/同调形态/档位/刷新行→treasure 视图
# _workshop_holder 子树。font_color 逐处 == STONE_DEEP;树链段已闭树界面。
main.ui.modals.camp.tree.closed.emit() # 树界面返回(关层归 presenter 单口)
await get_tree().process_frame
main.ui.modals.camp.scene.object_pressed.emit(&"chest") # 宝箱→秘宝匣(灰字四源位宿主)
await get_tree().process_frame
var ws_labels: Array = []
_collect_labels_by_text(main.ui.modals.camp.treasure._workshop_holder, ws_labels)
var locked_status: Array = _labels_with_text(ws_labels, "未解锁")
if locked_status.is_empty():
_problems.append("营地灰字:秘宝匣行「未解锁」态 label 未找到(wc4 夹具前提失守——新档应有锁定秘宝行)")
_problems.append("营地灰字:秘宝匣行「未解锁」态 label 未找到(wc4 夹具前提失守——新档应有锁定秘宝行)〔句柄子容器批 V1〕")
for l in locked_status:
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:秘宝行「未解锁」态未用 STONE_DEEP(wc4 :417/:476)")
_problems.append("营地灰字:秘宝行「未解锁」态未用 STONE_DEEP(wc4 :417/:476)〔句柄子容器批 V1〕")
var form_found := false
for l in camp_labels:
for l in ws_labels:
if l.text == "被动" or l.text == "主动·充能":
form_found = true
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:同调形态标签未用 STONE_DEEP(wc4 :509)")
_problems.append("营地灰字:同调形态标签未用 STONE_DEEP(wc4 :509)〔句柄子容器批 V1〕")
if not form_found:
_problems.append("营地灰字:同调行形态标签未找到(wc4 定位锚失守——同调段应有行)")
_problems.append("营地灰字:同调行形态标签未找到(wc4 定位锚失守——同调段应有行)〔句柄子容器批 V1〕")
var tier_vals := {}
for v in UiKit.TREASURE_TIER_LABELS.values():
tier_vals[String(v)] = true
var tier_found := false
for l in camp_labels:
for l in ws_labels:
if tier_vals.has(l.text):
tier_found = true
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:同调档位标签未用 STONE_DEEP(wc4 :513)")
_problems.append("营地灰字:同调档位标签未用 STONE_DEEP(wc4 :513)〔句柄子容器批 V1〕")
if not tier_found:
_problems.append("营地灰字:同调档位标签未找到(wc4 定位锚失守)")
_problems.append("营地灰字:同调档位标签未找到(wc4 定位锚失守)〔句柄子容器批 V1〕")
var refresh_inked := false
for l in camp_labels:
for l in ws_labels:
if String(l.text).contains("三选一刷新"):
refresh_inked = true
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:刷新行未用 STONE_DEEP(wc4 :525)")
_problems.append("营地灰字:刷新行未用 STONE_DEEP(wc4 :525)〔句柄子容器批 V1〕")
if not refresh_inked:
_problems.append("营地灰字:刷新行 label 未找到(wc4 定位锚失守)")
main._on_camp_closed()
_problems.append("营地灰字:刷新行 label 未找到(wc4 定位锚失守)〔句柄子容器批 V1〕")
main.ui.modals.camp.treasure.closed.emit()
await get_tree().process_frame
main.ui.modals.camp.scene.object_pressed.emit(&"board") # 布告板→图鉴占位(锁文宿主)
await get_tree().process_frame
var codex_labels: Array = []
_collect_labels_by_text(main.ui.modals.camp.codex, codex_labels)
var atlas_locks := _labels_with_text(codex_labels, "结构位预留")
if atlas_locks.is_empty():
_problems.append("营地灰字:图鉴锁文 label 未找到(wc4 定位锚失守——锚随形态订正去『柜台』)〔句柄子容器批 V1〕")
for l in atlas_locks:
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:图鉴锁文未用 STONE_DEEP(wc4 :167)〔句柄子容器批 V1〕")
main.ui.modals.camp.codex.closed.emit()
await get_tree().process_frame
main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3)
await get_tree().process_frame
main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点)
await get_tree().process_frame
if main.ui.modals.camp_overlay.visible or not main.ui.screens.start_overlay.visible:
_problems.append("营地关闭后未恢复开始层")
if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible:
_problems.append("营地关闭后未恢复开始层〔句柄子容器批 V1〕")
# ---- 选档界面行为走查(迭代 33 卡③):sl5 空槽新档链/sl6 有档槽进档链/sl7 删档二次确认双路径
# /sl8 消费方分派单口/sl12 动线序不回闪。开层=函数直调(「进入存档」钮归卡② mn3——中间态在案);
# 隔离=attach_slot_template 注入命名空间,夹具按槽直写(卡① 注入口)。----
@@ -208,7 +216,7 @@ func _ready() -> void:
new_btn.pressed.emit() # 真实按钮信号路径 → _on_save_select_new → create_slot+open_camp
for fi in 3:
await get_tree().process_frame
samples.append([main.ui.modals.save_select_overlay.visible, main.ui.modals.camp_overlay.visible, main.ui.screens.start_overlay.visible])
samples.append([main.ui.modals.save_select_overlay.visible, main.ui.modals.camp.scene.visible, main.ui.screens.start_overlay.visible])
if samples[0][0]:
_problems.append("[savesel] sl12 新档后选档层仍可见(落点开层未发生)")
elif not samples[0][1]:
@@ -217,7 +225,7 @@ func _ready() -> void:
_problems.append("[savesel] sl12 动线序违约:开始层在落点帧恢复(回闪一帧)")
if main.save_manager.slots.active_slot() != 1 or not FileAccess.file_exists(csel_tpl % 1):
_problems.append("[savesel] sl5 新档链失守(active=%d 应=1 且槽1 白板应落盘)" % main.save_manager.slots.active_slot())
var occluded: Array = main.ui.modals.flow.stack._occluded.get(main.ui.modals.camp_overlay, [])
var occluded: Array = main.ui.modals.flow.stack._occluded.get(main.ui.modals.camp.scene, [])
if not occluded.has(main.ui.modals.save_select_overlay):
_problems.append("[savesel] sl12 选档层被关闭而非栈记账挂起(先关后开形态——遮挡挂起中间态钉死)")
if int((JSON.parse_string(FileAccess.get_file_as_string(csel_tpl % 1)) as Dictionary).get("meta", {}).get("keystone_balance", -1)) != 0:
@@ -235,7 +243,7 @@ func _ready() -> void:
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())
if not main.ui.modals.camp_overlay.visible or main.ui.modals.save_select_overlay.visible:
if not main.ui.modals.camp.scene.visible or main.ui.modals.save_select_overlay.visible:
_problems.append("[savesel] sl6 落点未开营地层或选档未隐藏")
main._on_camp_closed()
await get_tree().process_frame
@@ -668,6 +676,51 @@ func _ready() -> void:
# 卡④大门 T5 接收,两头登记禁静默删除;续玩钮已退役,主菜单确认链已摘 mn6)。
# ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。
# ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)----
# 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。
# 无断点=两出口(是/否);有断点=三出口(set_breakpoint 注入选路停点形——同下方开场行③先例)。
# 判据绑可观测提交面:点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 恒=
# 无变更退回营地(写口零调用——余额读数不变+营地根 visible);开局后营地场景屏隐藏。
ProbeUiUtil.isolate_save(main, "smoke_click_probe_gate", true, false, true)
main._camp_pres.open_camp()
await get_tree().process_frame
var bal_gate: int = main._camp_data()["balance"]
main.ui.modals.camp.scene.object_pressed.emit(&"gate")
await get_tree().process_frame
if not main.ui.modals.confirm_dialog.is_open():
_problems.append("大门无断点:确认弹窗未开(点大门应先出确认不直接进冒险)〔营地图批 V3〕")
if main.run_scope.is_adventure:
_problems.append("大门无断点:未确认即进冒险〔营地图批 V3〕")
if main.ui.modals.confirm_dialog._secondary_btn.visible:
_problems.append("大门无断点:次钮应隐藏(两出口形态)〔营地图批 V3〕")
main.ui.modals.confirm_dialog.cancelled.emit()
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〕")
main.ui.modals.camp.scene.object_pressed.emit(&"gate")
await get_tree().process_frame
main.ui.modals.confirm_dialog.confirmed.emit() # 「是」→开始新冒险
await get_tree().process_frame
if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible:
_problems.append("大门确认:冒险未发起或营地场景屏未隐藏〔营地图批 V3〕")
# 有断点三出口(选路停点形注入——同开场行③ set_breakpoint 先例)
main.save_manager.set_breakpoint({
"master_seed": 42, "rng_counter": 0, "hero_hp": 30, "gp": 10, "keystone": 0,
"level_index": 1, "bonus": {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0},
"shop_purchased": {}, "relics": [], "keystone_drops": [], "bonus_taken": {},
"node_id": -1, "visited_ids": [], "graph_gen_v2": 1,
})
main._camp_pres.open_camp()
await get_tree().process_frame
main.ui.modals.camp.scene.object_pressed.emit(&"gate")
await get_tree().process_frame
if not main.ui.modals.confirm_dialog.is_open() or not main.ui.modals.confirm_dialog._secondary_btn.visible:
_problems.append("大门有断点:三出口形态失守(次钮应现形)〔营地图批 V3〕")
main.ui.modals.confirm_dialog.secondary_confirmed.emit() # 「开始新冒险」出口
await get_tree().process_frame
if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible:
_problems.append("大门有断点次确认:冒险未发起或营地未隐藏〔营地图批 V3〕")
# ---- 开场行三路径段(迭代36 wc1/wc2——真实入口驱动;隔离新档)----
# 驱动设计(0/0 白板局生存性不成立的实证处置——L2 记录进实现报告):
# ① 菜单开新局·白板反向=独立短局:0/0 滑条开局断言 L1 开场行零后缀(不开驱动——
-4
View File
@@ -45,15 +45,11 @@ func _pairs() -> Array:
["商店·爬价规则墨/暖亮页底", UiKit.WARM_SOFT, UiKit.BG_BRIGHT, 1.0],# 货架面爬价规则行(page_shell 亮底)
["商店·货架卡名墨/货架板", UiKit.INK_TITLE, UiKit.PANEL_DATA, 1.0], # bcard 货架卡商品名(换 TG 带后未机检位)
["商店·货架价签墨/货架板", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0], # bcard 价签「N ◈」(深金墨 5.21:1)
["营地·等级章字/头带深棕", UiKit.INK_ON_DEEP, UiKit.CAMP_HEADER_BAND, 1.0],# 锻造行 Lv 章(木底小章亮墨)
["营地·柜台头带字/头带深棕", UiKit.WARM, UiKit.CAMP_HEADER_BAND, 1.0], # 三柜台头带标题(暖白 bold)
["营地·铭牌副题墨/铭牌板", UiKit.STONE_DEEP, UiKit.PANEL_DATA, 1.0],# 营地铭牌副题「跨局成长账本…」(灰石深化 5.98:1)
["营地·行名墨/行板", UiKit.INK, UiKit.CAMP_ROW_PLATE, 1.0], # 锻造/工坊行名(墨族压暖米行板)
["营地·行价格墨/行板", UiKit.STONE_DEEP, UiKit.CAMP_ROW_PLATE, 1.0],# 锻造行「N 钥石」价签/工坊进度行(灰石深化 6.70:1)
["营地·树分组头墨/柜台板", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0],# 天赋树脉分组头「·锻造脉·」(深金墨 5.21:1)
["营地·树行名墨/柜台板", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # 天赋树行「名 Lv0/1」(标签深墨)
["营地·工坊状态墨/行板", UiKit.GOLD_INK, UiKit.CAMP_ROW_PLATE, 1.0],# 工坊状态字「已解锁」(深金墨 5.84:1·最不利现役态)
["营地·最近冒险墨/暖亮底", UiKit.WARM_SOFT, UiKit.BG_BRIGHT, 1.0], # 底栏最近冒险条带(暖白次级)
["设置·警示文/设置板", UiKit.INK_HINT, UiKit.PANEL_DATA, 1.0], # 设置警示文/后果说明(暖棕提示墨)
["设置·选项标签/设置板", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # 设置行标签/状态字(标签深墨)
["三选一·停点头部墨/暗页底", UiKit.WARM_SOFT, UiKit.BG_ROOT, 1.0], # 三选一停点头部/回血反馈行(page_shell 根底档)
+28 -18
View File
@@ -25,34 +25,44 @@ func _ready() -> void:
# 营地先行段只换 meta 不换 save_manager——冒险段换注零档在前)----
ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", false, false, false)
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_probe_camp"):
problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)")
problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)〔句柄子容器批 V1〕")
else:
await get_tree().process_frame
if not main.ui.modals.camp_overlay.visible:
problems.append("营地层未打开(进档开层新路径)")
if not main.ui.modals.camp.scene.visible:
problems.append("营地场景根未打开(进档开层新路径)〔句柄子容器批 V1〕")
elif main.ui.screens.start_overlay.visible:
problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)")
problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)〔句柄子容器批 V1〕")
else:
if not String(main.ui.modals.camp_overlay._balance_label.text).begins_with("钥石"):
problems.append("营地余额文案缺失(%s)" % main.ui.modals.camp_overlay._balance_label.text)
if main.ui.modals.camp_overlay._tree_buy_btns.is_empty():
problems.append("树淬炼柜台购买钮缺失(四柜台重排后树=主界面)")
# 天赋树子界面(场景点选真实路由——木桩命中区进树界面;卡④ T5 深员改指)
main.ui.modals.camp.scene.object_pressed.emit(&"dummy")
await get_tree().process_frame
if not main.ui.modals.camp.tree.visible:
problems.append("天赋树子界面未打开(场景点选路由中断)〔营地图批 V2〕")
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():
problems.append("树淬炼购买钮缺失(四柜台拆解后树=独立子界面)〔句柄子容器批 V1〕")
else:
var btn_by_id := {}
for card2 in main.ui.modals.camp_overlay._tree_buy_btns:
for card2 in main.ui.modals.camp.tree._tree_buy_btns:
btn_by_id[String(card2.node_id)] = card2.btn
if not btn_by_id.has("sp_hp") or bool(btn_by_id["sp_hp"].disabled):
problems.append("脊柱生命 L1 白板可购钮未启用(无门槛——can_buy 不含余额面语义)")
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 前置未亮应禁用(点亮前置语义)")
if main.ui.modals.camp_overlay._workshop_rows.size() != 12:
problems.append("秘宝匣柜台行数错误(%d ≠ 12——迭代 22 秘宝批换代)" % main.ui.modals.camp_overlay._workshop_rows.size())
main._on_camp_closed()
problems.append("同调位 2 前置未亮应禁用(点亮前置语义)〔句柄子容器批 V1〕")
main.ui.modals.camp.tree.closed.emit()
await get_tree().process_frame
# 秘宝匣子界面(场景点选真实路由——宝箱命中区)
main.ui.modals.camp.scene.object_pressed.emit(&"chest")
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()
await get_tree().process_frame
main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3)
await get_tree().process_frame
main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点)
await get_tree().process_frame
if main.ui.modals.camp_overlay.visible or not main.ui.screens.start_overlay.visible:
problems.append("营地关闭后未恢复开始层(开合往返失守)")
if main.ui.modals.camp.scene.visible or not main.ui.screens.start_overlay.visible:
problems.append("营地关闭后未恢复开始层(开合往返失守)〔句柄子容器批 V1〕")
# 换回全新零档(营地段购买态不泄入后续冒险段——确定性;先删后建防陈旧档泄入)
ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", true, false, true)
main.ui.modals.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机;本探针 start_single 第五路同吃此框,确定性禁依赖默认分支)
+96 -17
View File
@@ -39,36 +39,105 @@ func _ready() -> void:
_settle()
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_struct_probe_menu"):
problems.append("[camp] 营地入口缺失(选档层新档钮——mn9 新路径驱动中断)")
problems.append("[camp] 营地入口缺失(选档层新档钮——mn9 新路径驱动中断)〔句柄子容器批 V1〕")
else:
_settle()
await _audit("camp", [main.ui.modals.camp_overlay])
# 营地灰字源 token 扫描(迭代36 wc4 静态半——源内零裸 UiKit.STONE;判别式=
# STONE 出现次数 == STONE_DEEP 出现次数,即每处 STONE 都带 _DEEP 后缀)
var camp_src := FileAccess.get_file_as_string("res://presentation/views/camp_overlay_view.gd")
if camp_src.count("UiKit.STONE") != camp_src.count("UiKit.STONE_DEEP"):
problems.append("[camp] camp_overlay_view.gd 存在裸 UiKit.STONE(非 _DEEP 后缀——wc4 源扫描)")
main._on_camp_closed()
await _audit("camp", [main.ui.modals.camp.scene]) # 审计根=营地场景图(卡④ T5——CampOverlay 退役)
# 营地灰字源 token 扫描(迭代36 wc4 静态半——营地域视图源内零裸 UiKit.STONE;判别式=
# STONE 出现次数 == STONE_DEEP 出现次数;卡④ T5 改扫场景根+四子界面+公共壳六件)
var camp_stone := 0
var camp_stone_deep := 0
for vf in ["camp_scene_view", "forge_view", "treasure_view", "tree_view", "codex_view", "camp_sub_view_base"]:
var vf_src := FileAccess.get_file_as_string("res://presentation/views/%s.gd" % vf)
camp_stone += vf_src.count("UiKit.STONE")
camp_stone_deep += vf_src.count("UiKit.STONE_DEEP")
if camp_stone != camp_stone_deep:
problems.append("[camp] 营地域视图存在裸 UiKit.STONE(非 _DEEP 后缀——wc4 源扫描)〔句柄子容器批 V1〕")
# ---- 营地场景层序段(卡④ V1①——子节点序=layout 表 z 序逐层相等、各层纹理矩形与表逐值一致;
# 锻炉可达(V2 场景点选路由)+顶栏位在座(V8 结构)+根态反参照(V9——根态零文字按钮))----
var layout: CampSceneLayoutTable = main.db.presentation.camp_scene_layout
var order_ok := true
var rect_ok := true
for i in layout.layers.size():
var layer_node: Control = main.ui.modals.camp.scene.get_child(i)
if String(layer_node.name) != "Layer_%s" % layout.layers[i].key:
order_ok = false
if layer_node.position != layout.layers[i].texture_rect.position or layer_node.size != layout.layers[i].texture_rect.size:
rect_ok = false
if not order_ok:
problems.append("[camp] 场景子节点序≠layout 表 z 序(逐层相等失守)〔营地图批 V1〕")
if not rect_ok:
problems.append("[camp] 层贴图矩形≠layout 表(逐值一致失守)〔营地图批 V1〕")
main.ui.modals.camp.scene.object_pressed.emit(&"forge")
_settle()
main._on_save_select_closed() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点)
if not main.ui.modals.camp.forge.visible:
problems.append("[camp] 点选锻炉→锻造子界面未开(场景点选路由中断)〔营地图批 V2〕")
var hud_slot: Control = main.ui.modals.camp.scene.get_node_or_null("HudBandSlot")
if hud_slot == null or hud_slot.custom_minimum_size.y != 48:
problems.append("[camp] 顶栏 HUD 预留位缺失或高≠48(浮层覆盖不挤压画面)〔营地图批 V8〕")
var scene_buttons := 0
for n in main.ui.modals.camp.scene.get_children():
if n is Button:
scene_buttons += 1
if scene_buttons != 0:
problems.append("[camp] 场景根态存在文字按钮(反参照纪律——禁文字按钮列表形态)〔营地图批 V9〕")
main.ui.modals.camp.forge.closed.emit()
_settle()
main._on_camp_closed() # 关层收闭(营地与选档层同收+开始层恢复——卡④ T3)
_settle()
# ---- 生涯页:入口点击链(合成点击真路径=pressed.emit→组合根 _open_career)→
# ---- 生涯页:入口=帐篷旁笔记本(卡④ T3 改挂——场景点选信号真实路由→组合根 _open_career)→
# 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。
#(登记序/树序契约统一在全模态 _modal_registry_contract——boot 段跑)----
# 生涯页驱动=函数直调中间态(卡② mn9/mn10——careerbtn 已撤,卡④ 营地笔记本改挂前无 UI 路径)
main._open_career()
# 营地语境:开始层全程隐藏(笔记本入口在营地场景根——关生涯回营地不回开始层,卡④ T3 语境分支)
main._camp_pres.open_camp() # 重开营地场景根(笔记本入口语境)
_settle()
main.ui.modals.camp.scene.object_pressed.emit(&"notebook")
_settle()
if not main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible:
problems.append("[career] 生涯页未打开或开始层未隐藏(函数直调中间态断裂)")
problems.append("[career] 生涯页未打开或开始层未隐藏(笔记本改挂路径断裂)〔营地图批 V5〕")
await _audit("career", [main.ui.modals.career_overlay])
_career_readonly_contract(main)
_career_zero_motion_contract(main)
_career_nojump_contract(main)
main._on_career_closed()
_settle()
if main.ui.modals.career_overlay.visible or not main.ui.screens.start_overlay.visible:
problems.append("[career] 关闭后页未隐藏或开始层未恢复(模态栈单口失守)")
if main.ui.modals.career_overlay.visible or main.ui.screens.start_overlay.visible:
problems.append("[career] 关闭后页未隐藏或开始层回弹(营地语境分支失守)〔营地图批 V5〕")
# ---- Esc 浮窗结构段(卡⑤——三钮在位且仅三+零动效契约+登记序在册+debug_badge 不波及)----
main._camp_pres.open_camp()
_settle()
main.ui.modals.camp.scene.object_pressed.emit(&"gate")
_settle()
main.ui.modals.confirm_dialog.cancelled.emit() # 关大门弹窗(清场——浮窗段前提)
_settle()
var push_esc := func() -> void:
var ev := InputEventKey.new()
ev.keycode = KEY_ESCAPE
ev.physical_keycode = KEY_ESCAPE
ev.pressed = true
main.get_viewport().push_input(ev, true)
push_esc.call()
_settle()
if not main.ui.modals.camp.esc_overlay.visible:
problems.append("[esc] 浮窗未唤起(结构段前提——营地语境 Esc 路由)〔ESC批 V1〕")
var esc_btns := _buttons(main.ui.modals.camp.esc_overlay).size()
if esc_btns != 3:
problems.append("[esc] 浮窗三钮在位且仅三(实得 %d——禁第四钮)〔ESC批 V2〕" % esc_btns)
var esc_static := FileAccess.get_file_as_string("res://presentation/views/camp_esc_overlay.gd") + FileAccess.get_file_as_string("res://presentation/main_esc_flow.gd")
if esc_static.contains("create_tween") or esc_static.contains("UiKit.motion"):
problems.append("[esc] 浮窗双件源码存在动效令牌(契约 6 零动效——直开直关)〔ESC批 V7〕")
var esc_anim := _count_animplayers(main.ui.modals.camp.esc_overlay)
if esc_anim != 0:
problems.append("[esc] 浮窗子树 AnimationPlayer 非零(契约 6 零动效)〔ESC批 V7〕")
if main.ui.screens.debug_badge.visible:
problems.append("[esc] 浮窗开态 debug_badge 可见(发布态恒隐——float 类不注册 ModalStack 应零波及)〔ESC批 V8〕")
push_esc.call()
_settle()
if main.ui.modals.camp.esc_overlay.visible:
problems.append("[esc] 再按 Esc 未关浮窗(结构段收尾——Q2 单义切换)〔ESC批 V1〕")
main._on_camp_closed() # 关营地复归菜单语境(后续选档段前提)
_settle()
# ---- 选档界面(迭代 33 卡③):函数直调开层(「进入存档」钮归卡② mn3——点击链在卡② 承接),
# sl1 三槽呈现/sl2 摘要三项逐槽读数/sl3 摘要禁项/sl4 操作集二分/sl9 重启链不预选。
@@ -394,7 +463,7 @@ func _career_zero_motion_contract(main: Node) -> void:
# ⑫模态登记/树序全量合同(V1——career 专属登记序契约推广到全部模态+屏壳;
# 判据=main.ui.LAYER_CONTRACT 单一事实源:登记册序==合同模态序、直接子层树序==合同序逐对相等。
# 两特例按合同数据表达:level_screen 宿住冒险屏壳(树序随宿主=全模态之下,遮挡语义归模态栈
# screen_state 登记)、camp_overlay 成员与 camp_screen 屏壳同槽(屏壳树序代言成员登记位)。
# screen_state 登记)、camp_scene 成员与 camp_screen 屏壳同槽(屏壳树序代言成员登记位)。
# _career_registry_contract 的断言(career=状态面板后/确认弹窗前、树序同步)由本全量合同覆盖)
# ⑭选档界面契约(卡③ sl1/sl2/sl3/sl4/sl9——呈现面全部来自喂入数据,视图零自判)
func _save_select_contract(main: Node, tpl: String) -> void:
@@ -516,7 +585,7 @@ func _modal_registry_contract(main: Node) -> void:
elif lv.get_parent().get_index() >= main.ui.modals.legend_overlay.get_index():
problems.append("[registry] 冒险屏壳应在全模态之下(宿主 idx=%d legend idx=%d)〔装配生命批 V1〕" % [
lv.get_parent().get_index(), main.ui.modals.legend_overlay.get_index()])
# ④ 营地同槽特例:camp_screen 屏壳树序夹商店层与设置层之间(成员 camp_overlay 登记位代言)
# ④ 营地同槽特例:camp_screen 屏壳树序夹商店层与设置层之间(成员 camp_scene 登记位代言)
var cs: Node = main.ui.screens.camp_screen
if cs.get_index() <= main.ui.modals.shop_overlay.get_index() or cs.get_index() >= main.ui.modals.settings_overlay.get_index():
problems.append("[registry] camp_screen 屏壳树序应夹商店与设置层之间(idx=%d shop=%d settings=%d)〔装配生命批 V1〕" % [
@@ -719,3 +788,13 @@ func _start_columns_contract(main: Node) -> void:
if box.get_global_rect().intersects(right.get_global_rect()):
problems.append("[start][columns] 左列 %s 与右列 %s 相交(双列叠放回归——M2 缺陷闸)" % [
box.get_global_rect(), right.get_global_rect()])
# AnimationPlayer 全子树收集(零动效契约扫描面——_buttons 同款递归形态)
func _count_animplayers(node: Node) -> int:
var out := 0
if node is AnimationPlayer:
out += 1
for child in node.get_children():
out += _count_animplayers(child)
return out
+1 -1
View File
@@ -139,7 +139,7 @@ tests/
| 调参测速批量模拟(逐帧步进与统计读数) | 调参测量 | `TuneSimController.run()`(调试浮窗批量模拟入口) | `tune_sim_batch_parity_test.gd:_init`(配对对拍套件,随测速循环批建) |
| 战斗叙述与终局文案(数值带入/胜负互异) | 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()` 驱动) | `shot_probe.gd:_ready`(camp 截图段)〔营地图批 V1〕 |
| 营地场景图十层铺底与门洞留空呈现 | 营地场景视图 | 进入存档→选档→进档→营地场景屏(`CampSceneView.setup_layers()` 驱动);单元测试载入 `camp_scene_layout.tres` | `shot_probe.gd:_ready`(camp 截图段)〔营地图批 V1〕;`camp_scene_layout_test.gd:_test_layout_hotspot_geometry`〔营地图批 V14〕 |
| 营地七物件命中区点选与分界面可达 | 营地场景视图 | 七物件命中区 `_gui_input()` → `CampSceneView.object_pressed()` → 组合根开界面 | `ui_click_probe.gd:_ready`(七物件合成点击段)〔营地图批 V2〕 |
| 营地大门两分支确认与断点续玩接线 | 营地交互流 | 大门命中区 → ConfirmDialog(三出口)→ `main._on_confirm_new_adventure()`/`_resume_from_menu()` | `ui_click_probe.gd:_ready`(大门两分支段/断点确认段 :500-528)〔营地图批 V3〕 |
| 四柜台拆解后五界面入口改挂与写口零回归 | 营地五界面 | 五界面意图信号 → `CampShopPresenter` → `MetaProgression.tree_buy()` 写口 | `forge_workbench_test.gd:_test_forge_rows`〔营地图批 V4〕;`ui_click_probe.gd:_ready`(写口链段) |
+1
View File
@@ -192,6 +192,7 @@ func _suites() -> Array:
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
{"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
{"path": "res://tests/unit/presentation/camp_scene_layout_test.gd", "expect": 0}, # 营地场景布局数据件校验(V14——fail-fast 判据+七命中区几何红线)
{"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入)
+6 -6
View File
@@ -64,8 +64,8 @@ func _smoke_scene_split() -> void:
var camp: Node = load("res://presentation/scenes/camp_screen.tscn").instantiate()
root.add_child(camp)
await process_frame
check(camp.camp_overlay != null, "V2 营地屏壳独立实例化(camp_overlay 在位)")
check(not camp.camp_overlay.visible, "V2 营地层默认隐藏(模态纪律)")
check(camp.camp_scene != null, "V2 营地屏壳独立实例化(camp_scene 在位)")
check(not camp.camp_scene.visible, "V2 营地层默认隐藏(模态纪律)")
camp.queue_free()
var adv: Node = load("res://presentation/scenes/adventure_screen.tscn").instantiate()
root.add_child(adv)
@@ -103,10 +103,10 @@ func _smoke_scene_split() -> void:
chain_ok = false
chain_detail += " %s(%d)≤%s(%d)" % [chain_prev.name, chain_prev.get_index(), node.name, node.get_index()]
chain_prev = node
check(chain_ok, "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕" % chain_detail)
check(chain_ok, "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕〔句柄子容器批 V2〕" % chain_detail)
# ③ 探针挂载假设句柄在座(main.X 一级路径——探针迁移面收口证明)
check(main.ui.screens.start_overlay == main.ui.screens.menu_screen.start_overlay and main.ui.modals.camp_overlay == main.ui.screens.camp_screen.camp_overlay and main.ui.modals.level_screen == main.ui.screens.adventure_screen.level_screen and main.ui.screens.battle_root == main.ui.screens.adventure_screen.battle_root and main.ui.battle.board_zone.board_view == main.ui.screens.adventure_screen.board_view,
"V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)")
check(main.ui.screens.start_overlay == main.ui.screens.menu_screen.start_overlay and main.ui.modals.camp.scene == main.ui.screens.camp_screen.camp_scene and main.ui.modals.level_screen == main.ui.screens.adventure_screen.level_screen and main.ui.screens.battle_root == main.ui.screens.adventure_screen.battle_root and main.ui.battle.board_zone.board_view == main.ui.screens.adventure_screen.board_view,
"V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)〔句柄子容器批 V1〕")
check(main.run_scope != null and not main.run_scope.is_adventure,
"V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)")
main.queue_free()
@@ -118,7 +118,7 @@ func _smoke_scene_split() -> void:
func _topmost_visible_layer(main: Node) -> Node:
var candidates: Array[Node] = [main.ui.screens.start_overlay, main.ui.screens.battle_root, main.ui.screens.inter_overlay,
main.ui.modals.legend_overlay, main.ui.modals.node_overlay, main.ui.modals.level_screen, main.ui.modals.curio_overlay,
main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp_overlay, main.ui.modals.settings_overlay,
main.ui.modals.end_overlay, main.ui.modals.shop_overlay, main.ui.modals.camp.scene, main.ui.modals.settings_overlay,
main.ui.modals.status_panel, main.ui.modals.career_overlay, main.ui.modals.confirm_dialog, main.ui.modals.debug_overlay]
var best: Node = null
var best_x := -1
+13 -5
View File
@@ -51,21 +51,29 @@ 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:
print("--- 棋盘布局格边长公式(底部预留回归)---")
print("--- 棋盘布局格边长公式(底部预留回归·四档板型)---")
var board_generator = load("res://core/board_generator.gd").new()
var board_view = load("res://presentation/views/board_view.gd").new()
for L in [1, 2]:
var b = board_generator.generate(L, 0, _mk_stream("board_gen", 555), _db)
var board_view_script := load("res://presentation/views/board_view.gd")
# 四档取样表(board_gen.tres 实表取档——(L,D)→尺寸:10/12/13/14;13×13 与 14×14 首次入断言)
var tiers := [{"L": 1, "D": 0, "n": 10}, {"L": 2, "D": 0, "n": 12}, {"L": 3, "D": 0, "n": 13}, {"L": 3, "D": 9, "n": 14}]
for t_v in tiers:
var t: Dictionary = t_v
var b = board_generator.generate(int(t["L"]), int(t["D"]), _mk_stream("board_gen", 555), _db)
board_view.rebuild(b, null)
var avail := Vector2(1036, 460) # 受约束 D 区可用面积样本(1280×800 档量级——用户实测裁切场景)
board_view.set_cell_area(avail)
var n: int = maxi(b.width, b.height)
var cell: float = board_view.get("_cell")
var grid_h := n * int(cell) + (n - 1) * 2
Kit.check("L%d %dx%d 板网格高 %d ≤ 可用高 %d − 预留 24(格边 %dpx——下缘不贴边不裁切)" % [L, b.width, b.height, grid_h, int(avail.y), int(cell)],
Kit.check("L%d D%d 板型一致(%dx%d = 取样 %d——表改动致取样落错档即红)〔格边长 V3〕" % [int(t["L"]), int(t["D"]), b.width, b.height, int(t["n"])],
n == int(t["n"]))
Kit.check("L%d D%d %dx%d 板网格高 %d ≤ 可用高 %d − 预留 24(格边 %dpx——下缘不贴边不裁切)〔格边长 V3〕" % [int(t["L"]), int(t["D"]), b.width, b.height, grid_h, int(avail.y), int(cell)],
grid_h <= int(avail.y) - 24)
Kit.check("L%d 格边在 26–46 档内(%dpx)" % [L, int(cell)],
Kit.check("L%d D%d 格边在 26–46 档内(%dpx)〔格边长 V3〕" % [int(t["L"]), int(t["D"]), int(cell)],
cell >= 26.0 and cell <= 46.0)
Kit.check("L%d D%d 算式单点等价(set_cell_area 写入 %d == _cell_for 返回 %d——同一事实两路读取)〔格边长 V2〕" % [int(t["L"]), int(t["D"]), int(cell), board_view_script._cell_for(n, avail)],
int(cell) == board_view_script._cell_for(n, avail))
board_view.free()
@@ -0,0 +1,89 @@
extends SceneTree
# 营地场景布局数据件校验套件(V14——camp_scene_layout.tres 加载期 fail-fast 判据+七命中区几何红线机检)
# 覆盖:校验函数通过(键唯一/贴图在画布/命中成对/围墙带包络)· 七可点件键集 · 两两不相交 ·
# 不越画面 · 不侵顶栏禁区 · 篝火命中只取下半(关系谓词)· 门扇对齐门洞缺口 · 布告板挂围墙带。
# 断言全部为表内关系谓词——禁坐标字面量(数值单一事实源 = 数据行本表)。
# 运行:godot --headless --path game --script res://tests/unit/presentation/camp_scene_layout_test.gd(expect 0)
const Kit := preload("res://tests/unit/test_kit.gd")
const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest", &"notebook", &"board", &"campfire"]
func _initialize() -> void:
call_deferred("_test_layout_hotspot_geometry")
func _test_layout_hotspot_geometry() -> void:
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:
quit(Kit.finish("camp_scene_layout_test"))
return
var problems: Array[String] = table.validate()
Kit.check("V14 校验函数通过(问题清单空,实得 %s)〔营地图批 V14〕" % [problems], problems.is_empty())
var clickables: Array[CampSceneLayerSpec] = []
for spec in table.layers:
if spec.is_clickable():
clickables.append(spec)
Kit.check("V14 可点件数=7(实得 %d)〔营地图批 V14〕" % clickables.size(), clickables.size() == 7)
var got_keys: Array[StringName] = []
for spec in clickables:
got_keys.append(spec.hotspot_key)
got_keys.sort()
var want_keys: Array[StringName] = []
for k in _EXPECTED_KEYS:
want_keys.append(k)
want_keys.sort()
Kit.check("V14 命中键集=七物件绑定表(实得 %s)〔营地图批 V14〕" % [got_keys], got_keys == want_keys)
# 两两不相交(Rect2.intersects——贴边相触不算相交)
var disjoint := true
var pair := ""
for i in clickables.size():
for j in range(i + 1, clickables.size()):
if clickables[i].hotspot.intersects(clickables[j].hotspot):
disjoint = false
pair = "%s×%s" % [clickables[i].key, clickables[j].key]
Kit.check("V14 七命中区两两不相交(首违例 %s)〔营地图批 V14〕" % pair, disjoint)
# 不越画面+不侵顶栏禁区
var in_bounds := true
var offender := ""
for spec in clickables:
if spec.hotspot.position.x < 0.0 or spec.hotspot.position.y < table.hud_band_h \
or spec.hotspot.end.x > table.canvas.x or spec.hotspot.end.y > table.canvas.y:
in_bounds = false
offender = String(spec.key)
Kit.check("V14 命中区不越画面且不侵顶栏禁区(y≥%d,违例 %s)〔营地图批 V14〕" % [table.hud_band_h, offender], in_bounds)
# 篝火命中只取下半:命中区贴贴图底、严格小于贴图、起点在贴图内(火焰上段不可点)
var fire: CampSceneLayerSpec = null
var gate: CampSceneLayerSpec = null
var board: CampSceneLayerSpec = null
var wall: CampSceneLayerSpec = null
for spec in table.layers:
match String(spec.key):
"campfire": fire = spec
"gate": gate = spec
"board": board = spec
"wall": wall = spec
var fire_ok := fire != null and fire.hotspot.end.y == fire.texture_rect.end.y \
and fire.hotspot.size.y < fire.texture_rect.size.y \
and fire.hotspot.position.y > fire.texture_rect.position.y
Kit.check("V14 篝火命中只取下半(火堆贴底、火焰不点)〔营地图批 V14〕", fire_ok)
# 门扇命中对齐门洞缺口(表内 gate_gap 字段关系谓词)
var gate_ok := gate != null and gate.hotspot.position.x == float(table.gate_gap.x) \
and gate.hotspot.end.x == float(table.gate_gap.y)
Kit.check("V14 门扇命中对齐门洞缺口 %s〔营地图批 V14〕" % [table.gate_gap], gate_ok)
# 布告板挂围墙带:命中顶落在围墙贴图带内
var board_ok := board != null and wall != null \
and board.hotspot.position.y >= wall.texture_rect.position.y \
and board.hotspot.position.y <= wall.texture_rect.end.y
Kit.check("V14 布告板命中挂围墙带〔营地图批 V14〕", board_ok)
# 层序结构:10 层且 bg/wall 压前两位(z0/z1——子节点序=表序先验)
var order_ok := table.layers.size() == 10 \
and String(table.layers[0].key) == "bg" and String(table.layers[1].key) == "wall"
Kit.check("V14 层数=10 且 bg/wall 占 z0/z1〔营地图批 V14〕", order_ok)
# ConfigDB 装载链:表经单点注册进表现域容器且装载期校验过(fail-fast 钩子在位)
var db = load("res://core/config_db.gd").new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("V14 ConfigDB 装载含营地布局表且校验过(is_ready=%s)〔营地图批 V14〕" % db.is_ready,
load_ok and db.is_ready and db.presentation.camp_scene_layout != null)
quit(Kit.finish("camp_scene_layout_test"))
@@ -0,0 +1 @@
uid://ckyd57l11408
@@ -15,7 +15,7 @@ func _initialize() -> void:
func _row_by_id(ov) -> Dictionary:
var out := {}
for r in ov._forge._forge_rows:
for r in ov._forge_rows:
out[String(r.route_id)] = r
return out
@@ -30,7 +30,7 @@ func _run() -> void:
var sv = load("res://core/save_manager.gd").new(_TEST_SAVE)
sv.load_from_disk()
var mp = load("res://core/meta_progression.gd").new(db, sv)
var ov = load("res://presentation/views/camp_overlay_view.gd").new()
var ov = load("res://presentation/views/forge_view.gd").new()
root.add_child(ov)
var buys := []
var unlocks := []
@@ -42,16 +42,16 @@ func _run() -> void:
# ① 锁定态:树钥匙未点亮 → 工作间不可见、两区零行板(无任何可交互购买路径)
ov.open_camp({"balance": 999, "forge": mp.forge_shop_status(), "tree": []})
Kit.check("锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕",
not ov._forge._forge_workbench.visible and ov._forge._forge_lock_l.visible)
not ov._forge_workbench.visible and ov._forge_lock_l.visible)
Kit.check("锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕",
ov._forge._forge_zone_weapon.get_child_count() == 0 and ov._forge._forge_zone_armor.get_child_count() == 0)
ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0)
# ② 解锁态:钥匙点亮 → 两区各三路线
ov.open_camp({"balance": 500, "forge": mp.forge_shop_status(),
"tree": [{"node_id": "forge_workshop", "level": 1}]})
Kit.check("解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕", ov._forge._forge_workbench.visible)
Kit.check("解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕", ov._forge_workbench.visible)
Kit.check("两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕",
ov._forge._forge_zone_weapon.get_child_count() == 3 and ov._forge._forge_zone_armor.get_child_count() == 3)
ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3)
var rows: Dictionary = _row_by_id(ov)
Kit.check("六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕",
rows.has("sword") and rows.has("spear") and rows.has("staff")
@@ -52,7 +52,6 @@ func _init() -> void:
_smoke_diag_button_retired()
_status_panel_projection()
_career_overlay_projection()
_camp_recent_projection()
quit(Kit.finish("theme_projection_test"))
@@ -708,12 +707,6 @@ func _smoke_element_details() -> void:
var shop_src: String = FileAccess.get_file_as_string("res://presentation/views/shop_overlay_view.gd")
Kit.check("T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)",
shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER"))
var camp_src: String = FileAccess.get_file_as_string("res://presentation/views/camp_overlay_view.gd")
var camp_min := camp_src.count("custom_minimum_size = Vector2(0, 336)")
Kit.check("T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" % camp_min, camp_min == 3)
var camp_fill := camp_src.count("size_flags_vertical = Control.SIZE_FILL")
Kit.check("T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" % camp_fill, camp_fill == 4)
Kit.check("T5③ 三处锁文垂直居中在位", camp_src.count("SIZE_SHRINK_CENTER") >= 3)
# ---- 诊断转储钮冒烟(角落钮不在玩家画面+转储入口在调试浮窗;V6)----
@@ -796,20 +789,6 @@ func _all_labels(root: Node) -> Array:
return out
# ---- 营地最近冒险行投影(口径换代 V6:完成关数措辞钉子——open_camp 全量重建路径直调;
# 最小档 data=recent 单条 completed 局,其余键缺省走 .get 兜底——与组合根 _camp_data 投影同键面)----
func _camp_recent_projection() -> void:
print("--- 营地·最近冒险行投影(open_camp 最小档)---")
var camp = load("res://presentation/views/camp_overlay_view.gd").new()
camp.open_camp({"balance": 0, "recent": [
{"result": "completed", "levels_cleared": 2, "keystone_total": 5, "master_seed": 42}]})
var recent_text: String = String(camp._side._recent_list.text)
Kit.check("营地最近冒险行完成关数措辞(实际「%s」)〔口径换代 V6〕" % recent_text,
recent_text.contains("完成 2 关") and not recent_text.contains("通关"))
camp.visible = false
camp.free()
# ---- 生涯页只读投影一致性(汇总段六类九项逐行 == SaveManager 计数访问器/
# MetaProgression 访问器读数;投影拼装与 HudPresenter.career_data 同构(同访问器族同序)。
# 只读性连接巡检与零动效断言归 ui_struct_probe 呈现侧,本段只管数据面+旧档载入显示
@@ -0,0 +1,12 @@
# 迭代 46 GDD 反哺点清单——卡C(2026-11-01)
> 接收方:待办清单「反哺点专条」通道——用户会话逐条裁决后由文档批写入 `design/`,写入后本清单条目清掉(吸收即死)。
> 卡A/卡B 反哺条见同目录 `iter46-反哺点-2026-10-31.md`;本文件只收卡C v2.0。
1. **[battle.md §棋盘格尺寸公式 :144]** 「可读下限 30px——与结构钳制分开:钳制管算式不越界,下限管玩家能否看清」整句**退役**:整句删除,或改写为「格边长可读性以用户验收的现行画面为基准;结构钳制 [26,46] 管算式不越界」(措辞由文档批裁)。依据=2026-11-01 用户三项裁定(锚点=用户验收的现行渲染值,基座档 14×14=28px 达标;30 无实测背书)+停机呈报读数链(`reports/停机呈报-棋盘格边长口径收口批-2026-10-31.md`)+实现报告 §四锚点留档(`iter46-实现报告-卡C-2026-11-01.md`)。随句建议同节载明判据现行形态:全域禁人造格边长可读阈值,接线守卫=生产写入值与单点算式严格相等,最大板推定读数留痕不作判红。
2. **作废声明**:`iter46-反哺点-2026-10-31.md` 第 8 条(battle.md 算例表改数——随「30px 下限适用域」读法甲/乙裁决落文)**作废**——`:148-152` 算例表已随布局几何单点化批(1cdef6ba)退役,无落点;读法甲/乙之争已被 2026-11-01 用户三项裁定取代(下限句退役,不再有适用域问题)。文档批落文时以本条为准,10-31 清单第 8 条不再执行。
## 术语表
零新增术语——本批新名 `_cell_for` 属代码标识符,不入术语表(登记口径=概念规范名)。
@@ -0,0 +1,70 @@
# 迭代 46 实现报告——卡C 棋盘格边长口径收口批 v2.0(2026-11-01)
> 批次:迭代 46 卡C(v2.0 重立回编——2026-11-01 用户三项裁定后写卡批重立,合并提交 e7090bc9)。卡A/卡B 已于 2026-10-31 收口合回,见同目录 `iter46-实现报告-2026-10-31.md`。
> 工作区:`D:\code\g-plan2-iter46`(分支 iter46,卡C 段基点=master 5269ed40,WIP 三件按 v2 卡 §12 判定复用两件返工一件)。
> 需求卡片:`reports/需求卡片-棋盘格边长口径收口批-2026-10-30.md`(v2.0 定稿,零待裁点)。
## 一、卡C 状态
**收口**(T0–T4 全落)。游戏行为零变化:算式逐位同式、`set_cell_area` 副作用尾链零动、两条生产调用路径不改、画面像素零变化。本批改动全部落在守卫面=**人造阈值退役+算式单点+13/14 两档入卫**。
## 二、T0 改前基线读数(2026-11-01,master 5269ed40 态实测)
| 读数 | 结果 |
|---|---|
| `python tools/gdstyle_gate.py` | 扫描面 142 件、命中 5 条全在基线、基线外新违规 0、较登记值恶化 0(board_view.gd `max-public-methods=27`/`max-class-variables=31` 持平) |
| 单件跑 `board_gen_reward_test` | all green(**62 checks**——改前基线值) |
| 单件跑 `layout_probe` | 2 档 PASS(旧阈值形态「≥36px」消息,即本批退役对象) |
| 四档取档对账(`resolve_row(1,0)/(2,0)/(3,0)/(3,9)`,临时对账件跑后即删) | 10×10/12×12/13×13/14×14,与卡 §2 第 6 条全符 |
| `battle_layout.tres` 五字段 | stage_h=252/ap_bar_h=44/info_panel_w=240/log_band_h_dig=24/log_band_h_settle=84,与卡 §2 第 8 条全符 |
行号重锚:卡 §2 行号在 5269ed40 上逐一核实(`layout_probe.gd:15` MIN_CELL/`board_view.gd:206-215` 内联算式/`board_zone_view.gd:91-92` 生产调用路径+`board_view.rebuild` 末尾调用/`board_gen_reward_test.gd:53-69` 冒烟)——卡成文时点(3499638d)至 T0 时点间零漂移。`tools/gdstyle_baseline.txt` 两键登记值与卡 §2 第 5 条一致(:11=31、:22=27)。
## 三、实际对照表(改动—验收)
| # | 需求点 | 改动 | 验收测试(名称+实际落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 结果 |
|---|---|---|---|---|---|---|---|
| 1 | 探针阈值退役+格边长接线守卫 | `layout_probe.gd` MIN_CELL 常量整行删除;阈值断言换等价断言(:106-109) | 「两档几何不变量+格边长等价」`layout_probe.gd:_ready`(PASS 行 :45 尾缀〔格边长 V1〕;失败路径 :107) | 探针 | `godot --headless --path game res://probe/layout_probe.tscn` | 实读 `cell_size`(生产经 `set_cell_area` 真实写入)与 `_cell_for(n, bv.size)` 同输入严格相等——生产停止调单点算式即红;`_board` 内部字段直查=受控例外(板型 N 无公开出口,卡 §14.2);`grep -rn "MIN_CELL\|可读门槛\|可读下限" game/` 零命中(已跑,exit=1 零命中) | 绿(两档全过,基座实读 40px) |
| 2 | 算式单点(`_cell_for`) | `board_view.gd:210` 改调 `static _cell_for`(:226,逐位同式);:206 注释补唯一落点句 | 「算式单点等价」`board_gen_reward_test.gd:75-76`(尾缀〔格边长 V2〕) | 单元 | `godot --headless --path game --script res://tests/unit/core/board_gen_reward_test.gd` | `set_cell_area` 写入的 `_cell` 与 `BoardView._cell_for(n, avail)` 返回值逐档严格相等(同一事实两路读取,分叉即红) | 绿(四档 4 断言全过;既有四套断言全绿=等价性证明,62→74 checks) |
| 3 | 13×13/14×14 进入冒烟 | `board_gen_reward_test.gd:58` 循环集改四档取样表 [(1,0,10),(2,0,12),(3,0,13),(3,9,14)];:69-74 板型一致+既有两断言(前缀 L%d D%d) | 「格边长冒烟四档板型」同函数(尾缀〔格边长 V3〕) | 单元 | 同上 | 13/14 两档首次被断言:取档尺寸与实表一致、网格高 ≤ 可用高−24、格边 ∈ [26,46](既有断言逐字保留) | 绿(四档 12 断言全过,10/12/13/14 实测 41/33/31/28px) |
| 4 | 最大板推定守卫(留痕型) | `layout_probe.gd:_check_one` 零尺寸前置(:112-114 记红)+13/14 推定留痕(:116-119 采集,:47-50 PASS 后打印) | 「最大板推定留痕」同探针(尾缀〔格边长 V4〕) | 探针 | 同行 1 | ①零尺寸前置(size ≤ 0 记红);②推定读数打印并与本报告 §四留档锚点读数逐值对拍,不一致=卡 §9 L3 #2 呈报(禁实现侧改锚) | 绿(基座 30/28、全屏 46/46——与锚点逐值一致,零对拍偏差) |
| 5 | 批末收口 | 全批 | 代码域闸 `tools/run_all_gates.sh --code` | 全量闸 | `tools/run_all_gates.sh --code` | 所选域全绿回执(`game/tests/.gates/receipt.json`) | 绿(`all_green=true`,七闸 00/01/02/03/05/09/10,commit e3e206d0,域=code·scope=related[presentation],`changed_vs_baseline=[]`) |
**测试削弱申报核对**(卡 §8 申报全集四条,实际执行逐条对上):①探针阈值断言删除=申报 1 的执行面,补偿三件全落(等价断言/四档结构钳制/推定留痕对拍);②PASS 消息改写=申报 2;③循环集 2→4 档纯增量、样本值 (1036,460) 未改、既有两断言逐字保留=申报 3;④未发生恢复阈值、删旧断言、放宽区间、换读取入口任一项=申报 4 成立。另:既有几何不变量(三区非零/不越视口/互不重叠/view 含于 D 区)与视口档位表零改动(卡 §14.1 核对通过)。
## 四、锚点读数留档(两档视口 × 四板型——未来批次对照基准)
锚点=用户验收的现行渲染值(2026-11-01 用户裁,卡 §2 第 7 条)。本批探针实跑复核:
| 视口 | board_view.size(实读) | 10 板 | 12 板 | 13 板 | 14 板 |
|---|---|---|---|---|---|
| 基座 1280×800 | (1022.0, 454.0) | **40px(实读)** | 33px(单点算式读) | **30px(推定打印)** | **28px(推定打印)** |
| 16:9 全屏 1920×1080 | (1662.0, 734.0) | 46px(实读·钳制顶格) | 46px(单点算式读) | **46px(推定打印)** | **46px(推定打印)** |
基座档 10 板实读经等价断言与 `_cell_for(10, (1022,454))` 严格相等(V1 守卫当轮执行);13/14 推定由探针 V4 打印(当轮留痕);12 板与全屏 10/12 三格由单点算式读数件在两档真实可用面积上读出(一次性临时件,当轮跑批留痕后即删):基座 12 板=33px、全屏 10/12=46px。全表与卡 §2 第 7 条锚点逐值一致,零对拍偏差。
## 五、实机目检总清单(登记不阻塞)
1. **第三关 14×14 板格面数字清楚可辨**——2026-11-01 用户已实际背书现行画面(锚点裁定),登记照留(卡 T4)。
## 六、实现决定记录
代拍板白名单 6 项全部按卡执行(`_cell_for` 局部变量名与注释措辞/四档取样表用字典数组/探针消息文案与 PASS 措辞/尾缀〔格边长 V1–V4〕编排/`BoardViewScript` 常量名/`bv.get("_board")` 取值形态),零超范围裁决。超白名单备案三件(均属测量面承载,判据零扩张):
1. **探针 `_board == null` 前置**(`layout_probe.gd:104-105`)——卡片明列判据只有零尺寸前置与等价断言;null 前置防「内部直查遇 null 即脚本错误静默跳过剩余断言」(test-conventions §7 失败模式),属测量面防御。撤销方式=删该分支(等价断言改回无条件求值)。
2. **探针新增两成员 `_cell_reads`/`_est_reads`**(`layout_probe.gd:17-18`)——PASS 行基座实读与推定留痕打印的承载面;探针不在 S13 扫描面,无预算约束。撤销方式=删成员与两处打印(PASS 行退回无读数形态)。
3. **单点算式锚点全表读数件**(本报告 §四 12 板与全屏 10/12 三格补读)——卡 §2 第 7 条锚点表含四板型,探针打印只覆盖 13/14(卡定死),全表留档用一次性临时脚本对两档实读可用面积逐板型读 `_cell_for`(当轮跑批留痕后即删)。撤销方式=无(纯读数,不落码)。
## 七、遗留读数义务
零。本批无数值改动,MC/verifier 零跑(卡 §4 范围边界 6)。§四全表读数已当轮留痕,无待补。
## 八、S13 结构预算与批末闸对账
- `board_view.gd`:`max-public-methods` **27→27**(`_cell_for` 私有名不计数)、`max-class-variables` **31→31**(零新增成员——钳制界未外提)、无 NEW 键;gdstyle 全批「基线外新违规 0、较登记值恶化 0」。
- 探针与测试件不在 S13 扫描面(扫描面=产品码,测量面直构为既有惯例)。
- 批末代码域闸:`tools/run_all_gates.sh --code` 全绿(回执落 `game/tests/.gates/receipt.json`,T4 当轮执行)。
## 九、独立验收审计结论
独立验收审计由未参与实现的子代理执行,报告=`reports/iter46-验收审计-卡C-2026-11-01.md`。结论:**通过,可合并**——五项全过(对照表五行逐行核对/自跑最窄测试实读:探针基座实读 40px·推定 30/28·46/46·单元 74 checks 全绿/判据绑定面钉生产写入值/测试削弱逐处对上卡 §8 申报全集/全仓无人造阈值回归且无变异·撤销取证)。一处非阻断备注已处置:实现报告 §三 行 1 判据绑定面格补「受控例外」句(审计报告原文备注定格于补句前)。审计方自跑读数与本实现会话当轮实跑逐值一致。未过不得合并红线未触发。
@@ -0,0 +1,91 @@
# 迭代 46 验收审计——卡C 棋盘格边长口径收口批 v2.0(2026-11-01)
> 审计方:独立子代理(未参与实现,只读证据+自跑最窄测试,不采信实现报告转述)。
> 审计对象:commit `e3e206d0`(代码三件)+`8eaf7d87`(收尾文档两件);卡C 段基点 master `5269ed40`。
> 依据:需求卡片 v2.0(§8 改动—验收对照表+测试削弱申报全集)、实现报告 §三 实际对照表、批末闸回执。
> 审计工作区:`D:\code\g-plan2-iter46`(分支 iter46)。审计全程只读,唯一写入=本报告。
## 一、对照表逐行核对——通过
实现报告 §三 五行逐行核对,每行的验收测试真实存在、尾缀可 grep、真实入口未绕过改动点。
行 1(探针阈值退役+接线守卫):
- 测试真实存在:`game/probe/layout_probe.gd:98-109`(读 `cell_size` → 等价断言),失败路径 `:107`,PASS 行 `:45`。
- 尾缀 grep:`grep -n "格边长 V1" game/probe/layout_probe.gd` → `:45`/`:105`/`:107` 三处。
- 真实入口:`layout_probe.tscn:6` 挂载 `res://probe/layout_probe.gd`(场景进,非脚本直调);探针断言对象=`main.tscn` 实例化的生产 UI 链(`main.ui.battle.board_zone.board_view`),非测试替身。
- 报告附带的 grep 读数(MIN_CELL 零命中)由审计方独立复跑证实(见第五节)。
行 2(算式单点 V2):
- 测试真实存在:`game/tests/unit/core/board_gen_reward_test.gd:75-76`,断言条件 `int(cell) == board_view_script._cell_for(n, avail)`。
- 改动点行号核实:`board_view.gd:210`=`_cell = float(_cell_for(maxi(_board.width, _board.height), avail))`、`:226`=`static func _cell_for`——与报告所记 `:210`/`:226` 逐一相符。
- 真实入口:`--script res://tests/unit/core/board_gen_reward_test.gd` 直跑(文件头 `:10` 自述同入口)。
行 3(四档冒烟 V3):
- 测试真实存在:`board_gen_reward_test.gd:59` 四档取样表 `[(1,0,10),(2,0,12),(3,0,13),(3,9,14)]`,`:69`/`:71`/`:73` 三条 V3 断言。
- 真实入口同行 2(同一宿主 `_init:33` 调 `_smoke_layout_cell_formula`)。
行 4(推定留痕 V4):
- 测试真实存在:零尺寸前置 `layout_probe.gd:113-114`,推定采集 `:116-119`,PASS 后打印 `:47-50`,尾缀 `:49`/`:114`。
- 真实入口同行 1。
行 5(批末收口):
- `game/tests/.gates/receipt.json` 实读:`all_green=true`、`commit=e3e206d0f5646ecb8ece22ac08018c26e5591e1d`、七闸(00/01/02/03/05/09/10)exit 全 `0`、`domain=code`、`scope=related[presentation]`、`changed_vs_baseline=[]`——与报告行 5 所记一致。审计方未重跑全量闸(按审计约束,回执承载)。
## 二、自跑最窄测试抽查——通过
a. 探针两档(工作目录 `D:\code\g-plan2-iter46\game`):
- 命令:`/d/code/godot/Godot_v4.7.2-stable_win64_console.exe --headless --path . res://probe/layout_probe.tscn`
- 实测 exit 0。关键输出原文:
- `[layout_probe] PASS — 2 档几何不变量全过+格边长等价成立(基座实读 40px)〔格边长 V1〕`
- `[layout_probe] [基座 1280x800] 13 板推定 30px/14 板推定 28px(实板可用面积 (1022.0, 454.0))〔格边长 V4〕`
- `[layout_probe] [16:9 全屏 1920x1080] 13 板推定 46px/14 板推定 46px(实板可用面积 (1662.0, 734.0))〔格边长 V4〕`
- 读数与卡 §2 第 7 条锚点(基座 30/28、全屏 46/46,可用面积 1022×454/1662×734)逐值一致,零对拍偏差。审计方另按公式手工复核:基座 13 板 `(422−24)/13=30.6→30`、14 板 `(422−26)/14=28.28→28`、全屏 14 板 `(702−26)/14=48.3→钳制 46`——打印值与单点算式自洽。
b. 单元四档(同工作目录):
- 命令:`…console.exe --headless --path . --script res://tests/unit/core/board_gen_reward_test.gd`
- 实测 exit 0,收尾行 `[board_gen_reward_test] all green (74 checks)`。
- 四档各四断言全 PASS:L1D0(41px)/L2D0(33px)/L3D0(31px)/L3D9(28px)——每档「板型一致〔格边长 V3〕」「网格高 ≤ 可用高−24〔格边长 V3〕」「格边 ∈ 26–46〔格边长 V3〕」「算式单点等价〔格边长 V2〕」各一条,16 条尾缀齐全。62→74 增量=每档净增 3 条 × 4 档,账目相符。
c. gdstyle(工作目录 `D:\code\g-plan2-iter46`):
- 命令:`python tools/gdstyle_gate.py`
- 实测 exit 0:`扫描面 142 个 .gd;命中 5 条,基线内 5 条,基线外新违规 0 条,较登记值恶化 0 条`。
- 基线两键实读:`tools/gdstyle_baseline.txt:11`=`board_view.gd|quality/max-class-variables = 31`、`:12`=`board_view.gd|quality/max-public-methods = 27`——持平,无 NEW 键。
## 三、判据绑定面核查——通过(一处非阻断措辞备注)
1. 绑定的是生产写入值,不是多层防御后的终态:
- 探针取值面=`cell_size` 公开只读属性(`board_view.gd:89-91` getter 直返 `_cell`),`_cell` 由 `set_cell_area` 在 `:210` 直接写入;生产写入路径两条未动(`board_zone_view.gd:92` resized 信号→`set_cell_area(_board_area.size)`、`board_view.gd:273` rebuild 末尾)。等价断言 `layout_probe.gd:106` 对 `bv.size`(实板可用面积)严格相等(`!=` 记红)——生产停调单点算式即红(基座档默认值 46 与算式值 40 分叉可捕)。
- 单元取值面=真实调用 `set_cell_area(avail)` 后直读 `_cell`(`board_gen_reward_test.gd:65,67`),与同输入 `_cell_for(n, avail)` 逐档 int 严格相等(`:76`)——同一事实两路读取。
2. 内部字段直查两处的例外依据:
- 探针读 `_board`(`layout_probe.gd:103`):理由「板型 N 无公开出口」已载于代码注释 `:102`、卡 §7.3 T2 与卡 §14.2、报告 §六 白名单第 6 项。
- 测试读 `_cell`(`board_gen_reward_test.gd:67`):批前既有取值面(卡 §2 第 4 条记载改前即如此),卡 §8 申报 4 明列 `cell_size`/`_cell` 为两合法入口、禁换别的入口——属守住的旧面,非新开直查。
3. 非阻断措辞备注:卡 §14.2 与代码注释 `layout_probe.gd:102` 均称例外理由「对照表行 1 注明」,但实现报告 §三 行 1 判据绑定面格(以及卡 §8 行 1)未逐字带「N 无公开出口/受控例外」句——交叉引用指针落空。例外事实与理由已在他处完整承载(本节第 2 点所列四处),断言零削弱、无申报外改动,不判红;建议实现会话顺手把该句补进报告 §三 行 1(可选,不阻塞合并)。
## 四、测试削弱核查——通过
e3e206d0 负向行(删除行)全集逐处对账,全部落进卡 §8 申报四条,无申报外删改:
| 删除内容 | 对应申报 |
|---|---|
| `const MIN_CELL := 36.0` 整行 | 申报 1(阈值常量退役) |
| `if cell < MIN_CELL:` +「< 可读门槛」记红两行 | 申报 1(阈值断言删除) |
| 旧 PASS 消息(「格子 ≥36px」形态)两行 | 申报 2(文案随判据换代) |
| `for L in [1, 2]` 与 `generate(L, 0, …)` 两行 | 申报 3(循环集 2→4 档)+白名单第 2 项(字典数组形态) |
| 两条旧 Kit.check 的消息行 | 申报 3(前缀 L%d→L%d D%d——卡 §7.4 明许)+白名单第 4 项(尾缀编排) |
| 冒烟 print 头行(「底部预留回归」→「·四档板型」) | 申报 3 同批日志头随档数换代(非断言,无条件面) |
| `board_view.gd` 内联算式六行 | 非断言——逐字搬入 `_cell_for`(`board_view.gd:226-231` 对拍:SEP/DEPTH_PX 同源、reserve 24.0+d 同式、clampi(26,46) 留函数内),行为零变化由单元 74 checks 全绿承载 |
补偿三件全在位:等价断言(V1)、结构钳制四档(V3)、推定留痕(V4)。既有两断言条件逐字保留(`grid_h <= int(avail.y) - 24`、`cell >= 26.0 and cell <= 46.0`),样本 `Vector2(1036, 460)` 未改。既有几何不变量与视口档位表零改动(diff 负向行中无其一)。申报 4(禁恢复阈值/删旧断言/放宽区间/换读取入口)成立——四处均未发生。
## 五、全仓无人造阈值回归——通过
- `grep -rn "MIN_CELL\|可读门槛\|可读下限" game/` → 零命中(exit 1);扩到 `tools/`、`design/` 查 `MIN_CELL` 亦零命中。
- 30/36 阈值形态穷举:三件改动文件中 30 仅两处且均非格边长判据(`board_gen_reward_test.gd:203`「板面 30% 锚」=雷数比例锚、`board_view.gd:659`「南部 30%」=墙面口径注释);36 零出现。全 `game/` 断言形态 grep(`(>|>=|<|<=)\s*(30|36)`)命中皆为无关域(RNG 位移 `>> 30`、商店 `1 << 30` 哨兵价、护甲 `<= 30` 属性域检);`clampi` 无 30/36 形态。格边长唯一区间判据仍是公式自带 `clampi(26, 46)`。
- 变异类测试与撤销类取证:diff 负向行无「人为改错值验抓取」形态;无回退 hunk;测试代码无注入破坏分支(探针 `_board == null` 前置属测量面防御,报告 §六 第 1 项已备案,非变异)。
- design/ 现状:`battle.md:144`「可读下限 30px」句仍在位=预期(实现批禁写 `design/`,退役走反哺点通道——`reports/iter46-反哺点-卡C-2026-11-01.md` 第 1 条已登记,卡 §5 明示不阻塞本批)。
- 禁写面核查:卡C 段全区间 `5269ed40..8eaf7d87` 触碰文件恰为三件代码+两件 reports,`待办清单.md`/`体验验证清单.md`/`game/tests/README.md`/`design/` 零触碰。
- 工作区漂移核查:三件代码文件 `git status` 干净——审计实跑的即 e3e206d0 提交的字节。
## 六、总结论
**通过。** 五项审计全过:对照表五行真实入口与尾缀齐全;自跑探针两档绿(基座实读 40px、推定 30/28 与 46/46 合锚点)、单元 all green 74 checks(四档 41/33/31/28px,V2/V3 全 PASS)、gdstyle 基线外新违规 0 恶化 0;判据绑生产写入值与 `_cell_for` 严格相等;删改逐处对上申报四条;全仓无人造阈值回归、无变异/撤销形态。唯一遗留=第三节第 3 点的交叉引用措辞备注(不判红、不阻塞合并,补句归实现会话可选动作)。
+19
View File
@@ -0,0 +1,19 @@
# 迭代 47 反哺点清单(实现批未写 design/ 与根规则文件——逐条待用户裁决后由文档批落文)
- 接收方:待办清单「营地交互重做·五张需求卡片」条目(裁决后逐条清掉)+各 owner 文档
- 依据:卡⑥/卡④/卡⑤ §5 关联文档同步面全表义务格=反哺点·列清单
| # | 文档 | 位置 | 原句/现状 | 拟改 | 依据 |
|---|---|---|---|---|---|
| 1 | architecture.md | 呈现层模块清单/测试与质量域 | 无 UiCampModals/四子界面/CampRefreshBus/MainEscFlow/CampHudBand/CampSceneView 六新件行 | 增六件归属行(呈现层/组合根拆件)+LAYER_CONTRACT 槽变化(camp_overlay→camp_scene+四子界面+camp_esc_overlay)+产品码首个键盘输入入口(main._unhandled_input 契约 9 接线)注记 | 卡⑥/④/⑤ 落地实况 |
| 2 | camp.md | §顶栏 :39-41 / §叠层与开合 :43-45 | 「刷新入口=营地刷新单口(卡⑤同口挂接)」未记实装;承载形态「归卡④ §9 代拍板域,本 spec 不预设值」 | 回填实装状态(单口=CampRefreshBus 已挂/时机清单三条)+承载形态=单一内页切换(场景根留底)拍板值落文 | 卡④ T2/T3+卡⑤ 实装 |
| 3 | art-bible.md | §6.2 :393 围墙细条句 / §4.1 场景环境子板 | 「围墙层落盘按细条 (0,240,1280,70) 裁」;环境子板色值待登记 | 细条句补实装值 (0,261,1280,49)(Y 压缩桩身 43 锁定值精确,备选 A′ 在档);登记营地 32 色主色板(色值清单=artwork/2026-11-01-营地终稿后处理/换算式登记.txt,两道门合规零违例对) | 卡④ T1 实测+§0.3-3⑤ |
| 4 | camp-scene-prompts.md | §2.3 坐标表 / §4 验收 | 贴图矩形=规划值(130×140 等);单张 ≤32 量化后 | 八物件 contain 实测尺寸表(board 130×121 等——呈报备案追认值)+量化实况注记 | 卡④ T1 换算式登记 |
| 5 | 生涯.md | :72 营地行(P6 条带去向) | 「营地条带的去留是本提纲必答项——见 §五 P6」 | P6 注销(Q4=甲:条带随四柜台拆解撤下,逐局历史回看归生涯——本批已撤) | 卡④ Q4 裁定落地 |
| 6 | 08-Alpha版本标准.md | :193 「键盘 / 手柄输入(排 Beta)」 | Beta 行未注记 Esc 已落地 | 行内加注「例外:营地语境 Esc 浮窗已落地(用户明示裁决,澄清⑤裁决 9;键盘面仍排 Beta——本批只落 Esc 一项)」 | 卡⑤ 契约 2/9 |
| 7 | 待办清单.md | 营地交互重做条+UI 贴图重构条+图鉴挂起 | 条目状态「已编迭代 47」;贴图重构条对象注记;图鉴挂起 | 状态推进(完成即整条删除)+实况换代(三卡收口凭据);贴图重构条「四柜台对象变化」注记更新(柜台形态已退役);图鉴挂起条入口改挂注记 | 三卡实装 |
| 8 | 体验验证清单.md | 新增目检条 | — | 登记 §实现报告 §6 五条(场景观感/点选反馈/大门篝火/Esc HUD/四子界面)——登记不阻塞 | 卡④ V11+卡⑤ V9 |
| 9 | roadmap.md | 迭代 47 行+当前位 | 状态「待开工」 | 行换代(三卡实现报告列+状态)+当前位三行换代 | 迭代收口 |
| 10 | shot_checker 色数判据 | tools/shot_checker.py A 段 | camp 屏唯一色数 ≥64 | L3 呈报在案(读法 A/B 待裁)——裁决后判据修正随批+待办清单登记条 | 停机呈报 2026-11-01 |
| 11 | asset-path-map.md §3 / art-assets-checklist.md §3.5 | 营地 10 张路径行 | 登记超前注记 | 实装入库确认(scene_camp_* 十件在役+TEX_CAMP_* 十键消费)+终稿置换待用户游戏内终审注记 | 卡④ T1 |
| 12 | ui_kit CAMP_HEADER_BAND 令牌 | ui_kit.gd :178 邻域+ui_contrast 表 | 令牌在役(新界面零消费) | 令牌退役评估(现役消费面=ui_contrast 表已注销四行后零消费——随令牌批处置,非本批强删) | 卡④ T5 核签 |
+117
View File
@@ -0,0 +1,117 @@
# 迭代 47 实现报告(卡⑥ 营地模态句柄子容器批+卡④ 营地场景图新建批+卡⑤ Esc 浮窗与营地顶栏批)
- 接收方:roadmap.md 迭代 47 行(实现报告列)+待办清单「营地交互重做·五张需求卡片」条目
- 性质:代码实现批产出物(实现批唯一可写目录=reports/;design/ 与根规则文件零写入——反哺点清单另立)
- 分支:iter47(worktree g-plan2-iter47)· 基线 5ad72f44 · 收口提交史见 §9
## 1. 各卡状态
| 卡 | 状态 | 提交 | 出口验证 |
|---|---|---|---|
| 卡⑥ 句柄子容器批 | 收口 | 3a9c6ff4 | --code 全套闸 all_green;V1/V2/V3 留痕齐 |
| 卡④ 营地场景图批 | 施工完成(T6 出口闸等 shot_checker 裁决重跑) | aa4aa1dd/4b5e3f27/98470adc/5a0e7069/1243ffe6/52296872 | 七探针+system_test 全绿;V14 11 项绿;--code 闸 01-04/09/10 绿、05 渲染闸红=L3 呈报在案 |
| 卡⑤ Esc 浮窗与顶栏批 | 施工完成(T4 出口闸同上) | 4e4232d8 | 冒烟 PASS+system_test 69/0+七探针全绿 |
## 2. 卡⑥ 实际改动—验收对照表(尾缀〔句柄子容器批 Vn〕)
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 判据绑定面 | 结果 |
|---|---|---|---|---|---|---|
| V1 句柄迁移零行为漂移 | ui_camp_modals.gd 新件;ui_modals 持 camp 删平铺;main_ui_build 装配 10 处;改指面 8 文件 43 处 | 五探针营地段+system_test V1c:94-106+句柄同一性链 :108 | 探针+系统 | 现役营地断言逐件复跑(断言零改寻址新) | 全段绿=零漂移;全仓 grep 零 modals.camp.overlay 残留 | 绿(V1 尾缀 34 处 grep 在位) |
| V2 层序合同零漂移 | ui_registry node() 返回链改写(key 平铺不改名) | system_test V1c 合同树序链 | 系统 | LAYER_CONTRACT 单一事实源 | 树序链绿=合同落位零漂移 | 绿(V2 尾缀 :106 在位) |
| V3 S13 读数 | ui_modals 类变量 14→14 | gdstyle gate+grep -c '^var ' 计数 | 静态 | static_scan.sh | gate 绿零 NEW+计数=14 留痕(观测面局限:闸对 14/15 同零命中,人工计数承载) | 绿(143 件零 NEW;计数 14) |
## 3. 卡④ 实际改动—验收对照表(尾缀〔营地图批 Vn/Bn〕)
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 判据绑定面 | 结果 |
|---|---|---|---|---|---|---|
| V1 场景图形态 | camp_scene_view 10 层铺底+layout.tres+camp_screen 改挂 | shot_probe camp 段(截图=场景图)+ui_struct 场景层序段+shot_checker 结构段 | 探针 | 进存档→选档→进档→营地场景屏 | 截图实拍=场景图形态(十层合成/门洞透路/七物件就位);子节点序=表 z 序逐层;贴图矩形逐值 | 绿(截图目检+层序段 11 层断言) |
| V2 七物件可达+叠层退避 | 七命中区+object_pressed+组合根路由 | ui_probe/ui_click/ui_struct 场景点选段 | 探针 | 命中区→_gui_input→object_pressed→路由 | 锻炉/宝箱/木桩/布告板/笔记本/篝火/大门逐一 object_pressed 驱动可见性 | 绿(七件路由断言分散六探针全绿) |
| V3 大门两分支+开局收闭 | confirm_dialog 三钮化+消费方编排扩展+main_overlay_flow 路由 | ui_click 大门两分支段 | 探针 | 场景点选 gate→确认弹窗→分派 | 点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 写口零调用+营地根 visible;开局后营地隐藏 | 绿(无断点两出口+有断点三出口 set_breakpoint 注入全过) |
| V4 五界面功能+写口零回归 | 四 view 抽出+presenter 承接面+bus 单口 | forge_workbench_test(承载类同迁断言零改)+ui_click 购买链 | 单元+探针 | 意图信号→presenter→MetaProgression 写口 | 树购买出账+持久(reload 后级=1)/Fork 持久/锻造三意图——全链复跑绿 | 绿(12 项+点击链 0 问题) |
| V5 生涯只读契约 | career 入口改挂笔记本(本体零改) | ui_struct career 段(笔记本真实路由) | 探针 | 笔记本命中→object_pressed→_open_career | 生涯页可达+三契约不破+营地语境关生涯不回开始层 | 绿 |
| V6 图鉴挂起态 | codex_view 占位壳 | ui_click 灰字段(锚=结构位预留) | 探针 | 布告板命中→codex 开 | 占位锁文锚定+零激活禁行+非禁用态 | 绿 |
| V7 篝火动线 | 路由 campfire→二次确认→on_camp_closed | screen_flow 回退段+ui_click? | 探针 | 篝火命中(只下半)→确认→主菜单 | 二次确认 confirmed 先于关层;确认后营地+选档同收+菜单显 | 绿(screen_flow ③步+T4 孤检) |
| V8 顶栏位+刷新单口 | set_hud_band 预留位+camp_refresh_bus 新件 | ui_struct 顶栏位段+卡⑤ HUD 段 | 探针 | 写口尾→bus.refresh→现开界面+HUD | 顶栏位 48px 在座;写口尾一闸刷新(八尾改路由) | 绿 |
| V9 反参照纪律 | 场景根态零按钮 | ui_struct 反参照段 | 探针 | 场景根子树 Button 计数 | 根态零文字按钮(禁文字按钮列表形态) | 绿 |
| V10 美术约束 | shot_checker camp 骨架段+素材二器 | ui_contrast 令牌表+palette_check 读数 | 机检 | camp 截图+素材文件 | 见 §7 遗留读数义务(A 段色数判据=L3 呈报在案) | 部分(令牌 36 对绿;A 段红待裁) |
| V11 目检登记 | 实机目检总清单 | 件核对(§6) | 目检 | 用户实机 | 登记即义务了结(不阻塞) | 已登记(§6) |
| V12 探针迁移留痕 | F8 全量位点 | 件核对(本表+§8 迁移留痕) | 件核对 | 探针 diff 对账 | 逐点改前→改后 | 绿(§8) |
| V13 反哺点清单 | §5 同步面 | 件核对 | 件核对 | 反哺点文档逐条 | 每条有落点 | 绿(反哺点文档) |
| V14 layout 数据件校验 | schema validate+fail-fast 钩子 | camp_scene_layout_test.gd:_test_layout_hotspot_geometry(manifest 登记) | 单元 | 单测载入 .tres+ConfigDB 装载链 | 校验通过+七命中区几何红线+关系谓词零坐标字面量 | 绿(11 项) |
| B1 组合根接线总改造 | main.gd 路由/三钮编排/篝火/收闭+bus | ui_click 大门段+screen_flow 篝火段 | 集成 | 确认分派/_resume/_start/bus.refresh | 三钮消费方编排 open 前设后分派;cancelled 零写口;篝火确认离场 | 绿 |
| B2 CampOverlay 退役+四界面抽出 | overlay 删除+四 view+五句柄子容器 | forge_workbench_test+全仓件核 | 集成 | 五界面 open/close→模态栈单口 | 旧件零残留(件核一);承载类同迁断言零改 | 绿 |
## 4. 卡⑤ 实际改动—验收对照表(尾缀〔ESC批 Vn/Bn〕)
| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 判据绑定面 | 结果 |
|---|---|---|---|---|---|---|
| V1 Esc 唤起四态 | camp_esc_overlay+main_esc_flow+main._unhandled_input | screen_flow Esc 步 | 探针 | push_input 合成 Esc(真实键盘路径) | 根态唤起/再按关窗/echo 零响应/子界面无反馈/设置开态不误关/菜单语境不唤起 | 绿(10 项) |
| V2 浮窗三项 | setup_buttons 三钮 | ui_struct 浮窗段 | 探针 | 浮窗视图实例化 | 三钮在位且仅三(_buttons 递归计数) | 绿 |
| V3 返回主菜单 | _on_return_to_menu(首步 close 浮窗) | screen_flow 返回动线段 | 探针 | return_requested→动线 | 浮窗自收+营地隐+选档收+菜单显(四可见性) | 绿 |
| V4 设置双路径 | _on_settings+语境分支 | screen_flow 设置段 | 探针 | settings_requested→open→closed | 设置开浮窗恒 visible(树序遮挡非属性隐藏);关后回浮窗不回主菜单 | 绿 |
| V5 退出项 | _on_quit→_quit_game 复用 | 连接链断言(main_ui_build 接线在位) | 静态 | quit_requested→_on_esc_quit→_quit_game | headless 不可真退=静态判据+观测面局限申报(本表) | 绿(接线段随装配绿证) |
| V6 HUD 余额 | camp_hud_band+bus 挂册+进档尾刷新 | screen_flow HUD 段 | 探针 | 隔离档直写 _add_keystone(9999)→显式触发 bus.refresh | badge 文本=「钥石 9999」(单口现值,显式触发才变) | 绿 |
| V7 零动效 | 直开直关 | ui_struct 浮窗段 | 探针 | 双件源码+子树扫描 | 零动效令牌+AnimPlayer=0(扫描面>0 非空前置) | 绿 |
| V8 层序与遮挡 | contract 第六槽+node() | system_test V1c 复跑+ui_struct badge 段 | 系统+探针 | 合同表单一事实源 | 树序链含新槽逐对保序;badge 零波及 | 绿 |
| V9 目检登记 | 实机目检总清单 | 件核对(§6) | 目检 | 用户实机 | 登记即义务了结 | 已登记(§6) |
| V10 反哺点清单 | §5 同步面 | 件核对 | 件核对 | 反哺点文档 | 每条有落点 | 绿 |
| B1 Esc 接线与浮窗 | 输入入口/薄委托/视图/新槽 | screen_flow+ui_struct | 集成 | 合成 Esc→路由→flow.open/close | 四态路由绑现开集合与调用记录 | 绿 |
| B2 HUD 落位与刷新 | band 新建/set_hud_band/bus 挂册 | screen_flow HUD 段+ui_struct 顶栏段 | 集成 | bus.refresh→band.refresh_camp→badge | badge=单口取值;顶栏位结构在座 | 绿 |
| B3 设置语境分支与三出口 | _on_settings_closed 分支+三出口动线 | screen_flow 设置段+返回段 | 集成 | 三钮 pressed→MainEscFlow | 关设置回浮窗禁弹主菜单(反向断言);退出连接链 | 绿 |
## 5. 实现决定记录(代拍板追认单——随 commit 留痕汇总)
1. **承载形态=单一内页切换(场景根留底)**(卡④ §9-2 域内):模态栈 open 只隐上方登记层(modal_stack.gd:27 实测),四子界面登记于场景根之上且实底遮挡——双开互斥靠遮挡天然成立;V2 断言按留底派生(子界面开=场景根 visible 保持)。
2. **围墙层处理路线 A″**(用户裁 A 后处理实现):去底(边界泛洪+奶油描边屏障 BG_TOL=50)→门洞手术(门柱内移+克隆桩列,缺口 675→480 源=240 终稿精确)→Y 轴压缩(桩身 609→86 源=43 终稿锁定值精确,整带 692→98 落盘 (0,261,1280,49) 底锚 y310);备选 A′(带高 70/桩身 62)存档 artwork 同目录。竖向克隆接缝 3× 放大可辨、1× 游戏尺度不可辨(申报)。
3. **八物件 contain 落位尺寸**(呈报备案追认):board 130×121/gate 240×211/dummy 120×180/forge 240×193/tent 251×170/chest 106×90/notebook 100×66/campfire 160×149(锁定矩形 contain,命中矩形按锁定值);贴图锚定=底中心(帐篷/锻炉/宝箱/篝火/笔记本)+顶锚(门扇悬挂/布告板挂墙)。
4. **营地主色板 32 色**(§4.1 场景环境子板两道门合规):各层独立量化→并集 272→最近对合并 120 轮→32 色零违例对;十层按终版色板重量化回写。色值清单=换算式登记.txt,登记 art-bible §4.1 走反哺点。
5. **codex 锁文称谓订正**(L2):「柜台结构位预留」→「结构位预留」(零新文案——『柜台』随形态退役)。
6. **set_close_path 未落独立方法**(L2 API 面):Q2 关闭路径=组合根 Esc 路由直呼 flow.close(浮窗),视图无自关通路——卡面「关闭由组合根路由」直译;bind_signals 承载三钮意图外发。
7. **契约 9 分支序实证修正**:①先于②(设置开在浮窗之上态先命中无反馈)——首装②先被 screen_flow Esc 段实机场景证伪(2 红),按卡面编号归位后全绿。
8. **CampSceneView 初始隐**:_init visible=false(登记模态初始隐——boot 态场景根盖菜单三红同源修复)。
9. **ui_modals 头注/迁移面**:卡⑥ 头注换代(十二平铺+营地域子容器)✓;ui_struct 层序注释换代(camp_scene 代言)✓。
10. **卡④ T0 预算预案兑现**:main.gd 15/15 恰贴上限——T3/T4/T5 实现零新增成员(路由走 MainOverlayFlow 静态件+薄委托)✓。
## 6. 实机目检总清单(登记不阻塞——体验验证清单入册条目由文档批落)
1. 营地场景观感:十层构图/围墙门洞/纵深(V11——纵深稿「中景 2/3」可辨性)。
2. 七物件辨识与点选反馈:悬停轮廓+浮动+名牌/按下位移/锻炉宝箱禁用态(未解锁档)。
3. 大门两分支弹窗(三出口文案与位次)+篝火二次确认动线。
4. Esc 浮窗三动线+HUD 余额观感+浮窗视觉面(卡⑤ V9)。
5. 四子界面视觉(米板身壳/行板/返回钮位次)。
## 7. 遗留读数义务
1. **shot_checker A 段色数判据互斥**=L3 呈报在案(reports/停机呈报-shotchecker色数判据互斥-2026-11-01.md)——读法 A/B 待裁;裁决后判据修正随批+闸重跑追绿。
2. palette_check 读数(V10 素材级):15 张 0 红灯 0 新增色(只读模式)——48 令牌 baseline 目录在库缺位,双门机检化=工程批事项(反哺点登记)。
3. 用户在游戏内终审十层素材(2026-10-31 裁定预留)——终稿置换(若有)走反哺点。
## 8. 探针迁移留痕表(V12——F8 全量位点改前→改后)
| 位点 | 改前 | 改后 |
|---|---|---|
| screen_flow :54 营地旗标 | modals.camp.overlay | modals.camp.scene |
| screen_flow 回退段 :119-125 | overlay.closed.emit()+手动关选档 | 篝火动线(object_pressed campfire+confirmed) |
| screen_flow Esc 段(新增) | —(选档语境零响应段保留) | 十断言步 _step_camp_esc_and_hud |
| ui_probe 营地段 :26-57 | overlay 余额/树钮/秘宝行 | 场景点选路由→tree/treasure 视图成员 |
| ui_click 营地段 :60-250 | overlay 树链/灰字六源位 | 树链 tree view+灰字分扫 treasure/codex(锚去『柜台』)+savesel 三处场景根 |
| ui_click 断点段(卡② 移交) | lvbtn 驱动(已随卡② 撤) | 大门两分支断言段(V3 落位——无断点/有断点 set_breakpoint 注入) |
| ui_struct :45 audit | audit("camp",[overlay]) | audit("camp",[camp.scene]) |
| ui_struct 源扫描 :48-50 | camp_overlay_view.gd 单件 | 营地域六件合计(场景根+四子界面+公共壳) |
| ui_struct 生涯段 :54-69 | 函数直调 _open_career | 笔记本真实路由(open_camp 重入+object_pressed notebook+营地语境分支断言) |
| ui_struct 场景段(新增) | — | 层序/矩形逐值+锻炉可达+顶栏位+反参照+Esc 浮窗段 |
| ui_contrast 令牌表 :48-55 | 头带两行+铭牌副题+最近冒险 | 四行随形态注销(CAMP_HEADER_BAND 零消费随退);ROW_PLATE/PANEL_DATA 行保留 |
| shot_probe :120-135 | 关闭营地钮收闭 | 截图=场景图+篝火动线收闭 |
| system_test V2/:108/:121 | camp_overlay 成员/句柄 | camp_scene 三处机械随迁(T3) |
| system_test V3g camp 分支 | overlay 开合 | 场景根开合自适配(on_camp_closed 关层收闭) |
| forge_workbench_test :33 | 直载 camp_overlay_view+._forge._X | 直载 forge_view+扁平成员(断言零改 12 项绿) |
| theme_projection :711-716/:801-810 | 等高源码断言+最近冒险投影 | 注销(卡面削弱申报授权——旧形态断言失去对象/条带 Q4 甲撤下) |
## 9. 提交史与闸况
3a9c6ff4 卡⑥T1 → aa4aa1dd 卡④T1 进度 → 4b5e3f27 卡④T2 → 98470adc 卡④T3 → 5a0e7069 卡④T4 → 1243ffe6 卡④T5 → 52296872 卡④T5 补 → 4e4232d8 卡⑤T1-T3。
闸况:卡⑥ 出口 --code all_green(3a9c6ff4 对齐);卡④ T6 出口闸 01-04/09/10 绿、05 渲染闸红=shot_checker 判据互斥(L3 呈报在案——合回前重跑)。
## 10. 独立验收审计结论
(独立审计报告=reports/iter47-验收审计-2026-11-01.md——结论待审计回填:通过/缺漏/例外及依据。)
@@ -0,0 +1,60 @@
# 复核报告:迭代 47 双卡 grilling+godot-master 双审(2026-11-01)
- **接收方**:`reports/需求卡片-营地场景图新建批-2026-10-18.md`(卡④,v3.0→v3.1)+ `reports/需求卡片-ESC浮窗与营地顶栏批-2026-10-18.md`(卡⑤,v1.3→v1.4)。本报告是双审过程稿,裁断与订正已全部吸收进两卡,批末收尾按吸收即死退役。
- **审查构成**:四个独立子代理——卡④ grilling/卡⑤ grilling/卡④ godot-master/卡⑤ godot-master(全程只读、未落盘、未跑测试)。
- **发起**:用户令(开工前审查:零待裁点、零阻塞点、零灰色地带、符合 Godot 设计规范)。
- **裁决**:用户 2026-11-01 裁「都可以按照推荐来」——五裁点照推荐采纳、P3/P5 销号口径采纳、卡⑤ v1.4 补句、卡④ 漂移就地订正+Godot 风险项落卡。
## 一、双审结论
| 卡 | grilling | godot-master |
|---|---|---|
| 卡④ | 有条件可开工——卡内设计待裁点确为零、重立凭据属实、与卡⑤ 接口名实闭合;但 §0.3 四道开工门实证全部未销号(会卡 T1/T2/T5),另有成片现状事实漂移 | 基本符合有风险项——分层/信号/单点纪律全对齐、零硬违反;风险=1024×1024 源档降采样算式断裂+核心视觉不变量机器验收零覆盖 |
| 卡⑤ | 有条件可开工——零真待裁点(Q1/Q2/Q3 留痕完整);但三处现行文本会指导出错实现(浮窗残留缺陷级/V4 判据与契约 3 矛盾/Esc 第四态未定义) | 基本符合有风险项——浮窗选型/层序/拆件与现役先例同构、零硬违反;风险=文件表漏列 ui_modals/main_ui_build 两必改件+Esc 第四态连带语境分支谓词二义 |
## 二、裁断与销号落点(2026-11-01 用户裁)
| 项 | 裁断 | 卡内落点 |
|---|---|---|
| camp-scene-prompts §5 五裁点 | ①帐篷 300×170 追认现值②门扇 240×220 追认③布告板 (845,268,130,140) 挂围墙带④描边奶油色追认⑤环境子板色值不前置(T1 量化后按 art-bible §4.1 两道门登记;T5 色计数改条件步) | 卡④ §0.3-3/§9-4/§10;反哺点(§5 落文归文档批) |
| P3 点选反馈设计稿 | 销号口径=camp.md v2.2:35-37 四态规格即设计稿;名牌文案与可点区域视觉归 T2 域内 | 卡④ §0.2/§0.3-1 |
| P5 GDD/art-bible 落文 | 销号口径=形态落文完成;实装回填归 T6 反哺点义务,非停机条件 | 卡④ §0.2/§0.3-2 |
| T1 素材后处理发令 | /iter 开工令即含授权,不再两次发令 | 卡④ §0.3-4/T1 |
| 1024×1024 档降采样 | 逐张登记换算式;1024 档非整数缩放显式特批登记(L2+实现报告申报局限),不回炉(追认已过审终稿优先) | 卡④ T1 |
| Esc 第四态与接线 | 契约 9 四态优先序(判据单点=ModalStack 现开集合)+接线定稿=事件驱动 `_unhandled_input`+KEY_ESCAPE、禁 Input 单例轮询、不挂 ui_cancel、pressed 且非 echo | 卡⑤ 契约 9/T0/T1/§9-1 |
| 设置语境分支谓词 | =`camp_esc_overlay.visible`(谁开的设置谁收),与 Esc 守卫谓词分立 | 卡⑤ 契约 3 |
| 返回主菜单浮窗残留 | 动线首步 close 浮窗;V3 断言补浮窗 closed | 卡⑤ 契约 4/T3/V3 |
| camp 截图排序约束死信 | 废止原句;按迭代 47 执行序=卡④ 整卡先、卡⑤ 后,卡⑤ T2 限 HUD 区显式更新兜底 | 卡⑤ §0/卡④ T0 |
## 三、双审独立发现、交叉确证的灰色地带(全部落卡)
1. Esc 第四态(设置开在浮窗之上再按 Esc)——两审独立发现 → 契约 9①。
2. 返回主菜单后浮窗残留(缺陷级)——grilling 发现、godot-master 同域印证 → 契约 4。
3. V4 判据与契约 3 自相矛盾(浮窗恒 visible 由栈语义保证,非属性隐藏)→ V4 改述。
4. V6 badge 文本格式字面不等(带「钥石 」前缀)→ V6 改格式串比较。
5. CampRefreshBus「Bus」命名 vs 全局事件总线否决史(architecture.md:353)→ 卡④ T3 头注自陈+卡⑤ 措辞「挂接/接收推送」。
6. 文件表漏列 `ui_modals.gd`/`main_ui_build.gd`(S13 触面失察;ui_modals 类变量 14→15 恰贴上限)→ 两卡文件表与存量触面一查补列。
7. 「卡④ T5 晚于卡⑤ T2」排序约束单侧声明且按执行序天然不可满足 → 死信改述(见 §二)。
8. V7 零动效扫描面漏开合编排件 → 扫描面扩两新件;V5 补「禁对退出钮 pressed.emit」;V1 补冒险语境段与 echo 断言;「进档重读」触发主体钉组合根动线尾。
## 四、卡④ 成片事实漂移订正(T0 级,v3.1 就地订正)
`game/smoke/` 已退役(git 16c6f136)→ 探针全在 `game/probe/`;smoke_all.gd 已退役(git 957391e1)→ system_test.gd(V2 :67-68/V3g :520-541)+theme_projection_test.gd:710-717;`main.camp_overlay` 句柄已退役 → `main.ui.modals.camp_overlay`(ui_modals.gd:14);F3/F7 行号簇换 2026-11-01 实测;F6「断点弹窗现居主菜单」失实(main.gd:118-120 注释)——T4 承接;`_on_confirm_new_adventure` 已随卡②摘除;`UiKit.TEX_CAMP_*` 尚不存在(本批 ui_kit.gd 新增十键);`tools/gdstyle.sh` 不存在(→ static_scan.sh/gdstyle_gate.py);presenters/core 子目录路径订正;`design/gdd/06-待办清单.md` 已迁仓库根;契约 4 行号换实数(八处 :82/90/102/114/126/139/151/165=七口+on_camp_buy 双尾);终局落点 main.gd:703 → main_adventure_flow.gd:123-143。
## 五、卡⑤ F1–F10 抽验
10 条全抽:F1/F2/F3/F5/F6/F7/F8/F9/F10 属实;F4 消费点全对、行号三处系统性 ±1(keystone_badge :633→:632、modal_shell :487→:486、danger_button :700→:699)——已订正。卡外小漂:screen_flow_probe 营地旗标在 :54(:53 是菜单行)、待办清单五卡总条 :447 起——已订正。
## 六、Godot 规范审查补立(卡④)
- 节点选型钉死:Control 根+TextureRect 层+子节点序即 z 序;禁 CanvasLayer/z_index/Sprite2D 根(LAYER_CONTRACT 树序落位兼容)。
- V1 扩判据:camp_scene_view 子节点序=layout 表 z 序逐层相等、纹理矩形与表逐值一致;新增 V14 layout 数据件校验(fail-fast 命中区几何)——核心视觉不变量机器验收面补齐。
- T2 实现约束:浮动经 Tween 禁 `_process`;名牌 Label mouse_filter=IGNORE;禁用态去饱和经 ShaderMaterial;名牌画面内钳制;HUD 载体循 page_shell `PRESET_TOP_WIDE` 先例。
- layout.tres 接 ConfigDB schema 类(先例 battle_layout.tres)+加载期 fail-fast 校验。
## 七、遗留与开工判定
- **零待裁点、零阻塞点**:卡④ §0.3 四道开工门全部裁断销号;卡⑤ 零真待裁点、开工按序随卡④ 代码波(迭代内排序,非跨迭代阻塞)。
- 反哺点落文已完成(2026-11-01 本会话文档批,同批合回):camp-scene-prompts.md v1.3 §5 改已裁+art-bible.md v3.31 §6.2 待裁行改已裁;camp.md/art-bible 实装状态回填归迭代实现批 T6 义务。
- 机检预算互斥裁决(2026-11-01 用户裁,停机呈报=iter47 worktree):实现段实测两卡平铺合计 ui_modals.gd 类变量 19>S13 上限 15——裁**最小改动**=营地六句柄落 `ui_camp_modals.gd` 子容器(新立卡⑥ 前置基建,迭代 47 执行序第一位;卡④ v3.2/卡⑤ v1.5 随批改口径,卡⑤ v1.4「14→15 恰贴」算术缺陷一并订正);全量分域句柄分家登记待办清单另立批。双审预算审为单卡口径、未做跨卡同文件累加核对,已在裁决中注记。
- **判定:迭代 47 可开工**(卡⑥ → 卡④ → 卡⑤ 按序,`/iter` 发令即起 T0)。
@@ -1,19 +1,19 @@
# 需求卡片:Esc 浮窗与营地顶栏批(营地顶部小 HUD 栏 · Esc 三钮浮窗)· 营地交互重做卡⑤
- 版本:**v1.3(现行有效卡)**。留痕:Q1/Q2/Q3 均于 2026-10-18 裁定(甲/甲/甲,见 §10,已裁非待裁);契约 2 随 Q3 升格;卡④ 2026-10-31 已重立为 v3.0 现行有效卡,本卡随卡④ 代码波同行(改动沿革只进 git commit log)。
- 版本:**v1.5(现行有效卡)**。留痕:Q1/Q2/Q3 均于 2026-10-18 裁定(甲/甲/甲,见 §10,已裁非待裁);卡④ 2026-10-31 重立、2026-11-01 修订为 v3.1,本卡随卡④ 代码波同行;2026-11-01 grilling+godot-master 双审后用户令修订(v1.4)——契约 9 新立、契约 3 谓词钉死、契约 4 浮窗自收、V4/V6 订正、死信约束改述、文件表补两件;2026-11-01 用户裁机检预算互斥停机呈报(v1.5)——浮窗句柄改落 `ui_camp_modals.gd` 子容器(卡⑥ 前置建件,本卡 T1 落第六枚),`ui_modals.gd` 类变量 14 不升(原「14→15 恰贴」句作废),契约 7 补寻址形态。
- 来源:[需求澄清-营地交互重做-2026-10-18.md](需求澄清-营地交互重做-2026-10-18.md)(唯一事实源:⑤裁决 9/10、④结构树语境、⑦切批原则⑤)
- 归属:呈现层/组合根(`game/presentation/`)——营地交互重做五卡之五
- 排期:未编入(2026-10-18 用户令:需求卡片不预占迭代号,批次归属按工作量另裁)
- 开工门:U0(用户 `/iter` 发令)+ 零待裁点(Q1/Q2/Q3 已裁,见 §10)+ grilling 复核门过+ **卡④ 代码波落地后**(卡④ 2026-10-31 已重立为 v3.0 现行有效卡、设计线闭合;本卡浮窗与 HUD 落在卡④ 新建的场景屏上,排期随卡④ 代码波之后,卡片本身有效)
- 开工门:U0(用户 `/iter` 发令)+ 零待裁点(Q1/Q2/Q3 已裁,见 §10;2026-11-01 双审零新增真待裁点)+ grilling 复核门过+ **卡④ 代码波落地后**(卡④ v3.1 现行有效卡、设计线闭合、开工门已裁断销号;本卡浮窗与 HUD 落在卡④ 新建的场景屏上,排期随卡④ 代码波之后,卡片本身有效)
## 0. 前置依赖与并行性
- 前置卡④(营地场景图,2026-10-31 已重立为 v3.0 现行有效卡):Esc 浮窗的「返回主菜单」与顶栏 HUD 都落在卡④ 新建的营地场景屏上;卡④ 设计线已闭合(样张过审 2026-10-31+art-bible v3.24 §6.2)——本卡不可先于卡④ 代码波落地,排期随④ 之后;本卡自身零美术依赖(零新素材、裁决 9/10 已给全),设计前置检查通过,卡片有效。
- **跨卡接口(保持既有名,不改名)**:①场景屏顶栏位(卡④ T2 预留;卡④ v3.0 挂接签名=`camp_scene_view.gd set_hud_band()`,「卡⑤ 填值」);②**营地刷新单口**(卡④ T3 定义;卡④ v3.0 定名 `camp_refresh_bus.gd` 的 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`)——本卡 HUD 刷新挂该单口,**非已退役的 `refresh_camp`**;③子界面返回钮承诺(卡④ T3——Q1 甲案前提:五界面各带返回/关闭钮);④卡④终局落点不变量(T0 互证:现役=终局 continue 回主菜单,`main_adventure_flow.gd:124-143` 终局出口 `_begin_session(false)`,行号 T0 复核;若后续改落点为营地则本卡「返回主菜单」动线须补会话收束)。卡④ v3.0 已原样承接上述四项,接口名无改动。
- 前置卡④(营地场景图,v3.1 现行有效卡——2026-11-01 双审修订、开工门已裁断销号):Esc 浮窗的「返回主菜单」与顶栏 HUD 都落在卡④ 新建的营地场景屏上;卡④ 设计线已闭合(样张过审 2026-10-31+art-bible v3.31 §6.2)——本卡不可先于卡④ 代码波落地,排期随④ 之后;本卡自身零美术依赖(零新素材、裁决 9/10 已给全),设计前置检查通过,卡片有效。
- **跨卡接口(保持既有名,不改名)**:①场景屏顶栏位(卡④ T2 预留;卡④ v3.1 挂接签名=`camp_scene_view.gd set_hud_band()`,「卡⑤ 填值」);②**营地刷新单口**(卡④ T3 定义;卡④ v3.1 定名 `camp_refresh_bus.gd` 的 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`)——本卡 HUD 刷新挂该单口,**非已退役的 `refresh_camp`**;③子界面返回钮承诺(卡④ T3——Q1 甲案前提:五界面各带返回/关闭钮);④卡④终局落点不变量(T0 互证:现役=终局 continue 回主菜单,`main_adventure_flow.gd:123-143` 终局出口 `_begin_session(false)`,行号 T0 复核;若后续改落点为营地则本卡「返回主菜单」动线须补会话收束)。卡④ v3.1 已原样承接上述四项,接口名无改动;场景根句柄名与 `LAYER_CONTRACT` 槽名由卡④ T0 定稿(显式验收点),本卡只消费不改名。
- 卡②的退出游戏钮、现役设置面——本批只复用不重做(契约 3)。
- 与卡④同批或随后(两卡触碰同一新屏)。**探针入口路径依赖卡②③链**:营地交互重做后营地只能经「菜单三钮→进入存档→选档→进档」到达——本卡 V1/V3 探针段隐含依赖该全链在位;卡②③ 工程波已随迭代 33 合回(待办清单 2026-10-18 条实况),探针段全链在位,无挂起前提。
- **多卡共改文件分界(`screen_flow_probe.gd`)**:卡④ T5 改该文件营地键语义(:53→新场景根句柄)与回退段(:119-125);本卡 T3 扩展该文件 Esc 合成段(现 :153-165 已有「选档语境 Esc 零响应」断言)为营地语境 Esc 浮窗断言段。两段按行域分界,各自改各自段,禁互相覆盖;T0 与卡④ 对齐行域。
- **camp 截图/golden 联动**:HUD 栏落营地场景屏会改 camp 截图内容——若本批晚于卡④单跑,卡④ 刚重制的 camp 断言基线必再漂移;若同批,卡④ T5 迁移时点须晚于本卡 T2 HUD 落位(排序约束)。本卡 T2 含「HUD 落位后核漂移热区→限 HUD 区显式更新+呈报」条;现无 camp 逐像素 golden(卡④ 契约 10)。
- **多卡共改文件分界(`screen_flow_probe.gd`)**:卡④ T5 改该文件营地旗标语义(:54→新场景根句柄;:53 是菜单行——v1.4 订正)与回退段(:119-125);本卡 T3 扩展该文件 Esc 合成段(现 :153-165 已有「选档语境 Esc 零响应」断言)为营地语境 Esc 浮窗断言段。两段按行域分界,各自改各自段,禁互相覆盖;**分界锚=断言段语义/段名,不锚行号**(一侧插删行会平移另一侧行号,2026-11-01 双审补立);T0 与卡④ 对齐。
- **camp 截图/golden 联动(v1.4 改述,原排序约束句废止)**:HUD 栏落营地场景屏会改 camp 截图内容。按迭代 47 执行序(roadmap 迭代总表格内排列顺序=执行顺序——卡④ 整卡先行、卡⑤ 后),卡④ T5 的 camp 断言基线建立在**无 HUD 场景屏**上;本卡 T2 HUD 落位后核漂移热区→限 HUD 区显式更新+呈报(T2 已列该条)。原 v1.3「卡④ T5 迁移时点须晚于本卡 T2 HUD 落位」句在上述执行序下天然不可满足(死信),2026-11-01 用户裁改述如上;现无 camp 逐像素 golden(卡④ 契约 10)。
## 1. 目标与完成定义
@@ -32,15 +32,15 @@
4. 浮窗零动效直开直关(生涯页同款契约);新层入 `LAYER_CONTRACT`;
5. 批末重闸按改动域选档全绿;三份 `reports/` 产出物写齐(T4)。
## 2. 现状事实(2026-10-18 实查;2026-10-31 复核行号,开工日 T0 再核)
## 2. 现状事实(2026-10-18 实查;2026-10-31 复核行号;2026-11-01 双审全量复核——F4 行号 ±1 漂移订正、F9 补全路径,开工日 T0 再核)
| # | 事实 | 出处 |
|---|---|---|
| F1 | **产品码零键盘快捷键处理**:`KEY_ESCAPE`/`ui_cancel`/`ui_accept` 在 `game/core/`+`game/presentation/` grep 零命中——Esc 浮窗产品码从零新建,无旧路径可迁。**但探针侧已有键盘合成先例**:`screen_flow_probe.gd:156-160` 用 `InputEventKey`(keycode=`KEY_ESCAPE`)经 `Viewport.push_input(ev, true)` 合成,断言「选档语境 Esc 零响应」(:153-165,卡⑤ 范围外的零实现断言)——键盘事件合成机制已实证,T0 由「从零 spike」改为「复核该先例并迁移到营地语境」。鼠标 `push_input` 先例=`ui_input_probe.gd:84-116`(`InputEventMouseMotion`/`InputEventMouseButton`;原 `game/smoke/` 目录已不存在,全部探针在 `game/probe/`) | `game/probe/screen_flow_probe.gd:153-165`;`game/probe/ui_input_probe.gd:84-116` |
| F2 | 设置面在役:开=`_open_settings()`(`main.gd:444` 薄委托→`main_overlay_flow.gd:7-9`:`start_overlay.visible=false`+`ui.modals.flow.open(settings_overlay)`);关=`_on_settings_closed()`(`main.gd:448`→`main_overlay_flow.gd:11-13`:`ui.modals.flow.close`+**`start_overlay.visible = true` 硬副作用**——营地语境照抄会在关设置后弹主菜单,须语境分支,契约 3)。开合走组合根模态编排口 `ui.modals.flow.open/close`;静态承接件是 main 同名函数逐字搬运、main 侧留薄委托,探针 `main.X` 路径零改动。`_open_settings`/`_quit_game` 菜单钮调用点见装配段(T0 复核行号) | `main.gd:444`/:448;`game/presentation/main_overlay_flow.gd:7-13` |
| F3 | 退出游戏在役:`_quit_game()`(`settings_store.save_settings()` 后 `get_tree().quit()`;无语义副作用,可原样复用) | `main.gd:143-146` |
| F4 | 钥石徽工厂件在役:`UiKit.keystone_badge` 定义 `ui_kit.gd:633`;消费点四处产品码——营地顶栏 `camp_overlay_view.gd:125`/生涯页 `career_overlay.gd:54`/状态面板 `status_panel.gd:32`/选档界面 `save_select_overlay.gd:86`(卡③ 新增),+ `theme_projection_test.gd:853-854` 单测投影断言。HUD 栏复用该件,零新素材 | `game/presentation/views/ui_kit.gd:633`;`camp_overlay_view.gd:125`;`career_overlay.gd:54`;`status_panel.gd:32`;`save_select_overlay.gd:86` |
| F5 | art-bible **v3.24** §6.2 现载「钥石余额:常驻画面顶部的一行小 HUD 栏(高 48px,浮层覆盖,不挤压画面)」(:396);camp.md v2.2 §顶栏(:39-41)同述+「刷新入口=营地刷新单口(卡⑤同口挂接)」——与裁决 10 同向,本批把呈现位从 CampOverlay 顶栏迁场景屏 HUD | `design/art/art-bible.md:396`;`design/gdd/ux/camp.md:39-41` |
| F4 | 钥石徽工厂件在役:`UiKit.keystone_badge` 定义 `ui_kit.gd:632`;消费点四处产品码——营地顶栏 `camp_overlay_view.gd:125`/生涯页 `career_overlay.gd:54`/状态面板 `status_panel.gd:32`/选档界面 `save_select_overlay.gd:86`(卡③ 新增),+ `theme_projection_test.gd:853-854` 单测投影断言。HUD 栏复用该件,零新素材 | `game/presentation/views/ui_kit.gd:632`;`camp_overlay_view.gd:125`;`career_overlay.gd:54`;`status_panel.gd:32`;`save_select_overlay.gd:86` |
| F5 | art-bible **v3.31** §6.2 现载「钥石余额:常驻画面顶部的一行小 HUD 栏(高 48px,浮层覆盖,不挤压画面)」(:396);camp.md v2.2 §顶栏(:39-41)同述+「刷新入口=营地刷新单口(卡⑤同口挂接)」——与裁决 10 同向,本批把呈现位从 CampOverlay 顶栏迁场景屏 HUD | `design/art/art-bible.md:396`;`design/gdd/ux/camp.md:39-41` |
| F6 | 层序唯一事实源=`ui_registry.gd:15-35 LAYER_CONTRACT`(`UiRegistry`;`main.gd:5` 头注自陈界面句柄与层清单归 ui 注册面);全仓唯一落位口=`ui_registry.gd:76 apply_layer_contract(root)`(唯一 `move_child` 点);模态登记册序/树序断言=`system_test.gd:94-106`(V1c 合同树序链)。浮窗新槽入该合同表,禁散落 `move_child` | `game/presentation/ui_registry.gd:15-35`/:76;`game/tests/system_test.gd:94-106` |
| F7 | 零动效契约先例:生涯页/状态面板「直开直关」——`ui_struct_probe.gd:379-391 _career_zero_motion_contract`(子树 AnimationPlayer 零命中+视图源码 `create_tween`/`UiKit.motion` 令牌扫描)、:334-341 `_status_zero_motion_contract` 同形 | `game/probe/ui_struct_probe.gd:334-341`/:379-391 |
| F8 | 08「六 · Alpha 不含」载「键盘 / 手柄输入(排 Beta)」行(**:193**——:192 是反雪球机制行)——Esc 属用户明示裁决(澄清⑤裁决 9),与该行的关系须同步注记(反哺点),本批不自扩其余键盘面 | `design/gdd/08-Alpha版本标准.md:193` |
@@ -53,18 +53,25 @@
🔴 **契约 2 · Esc 范围=营地语境(Q3 已确认,2026-10-18)**:浮窗只在营地场景图语境唤起(含其子界面开态——行为按 Q1 裁定的甲案:只根态响应)。**禁扩展到战斗/地图/其他屏**(用户 2026-10-18 确认「只在营地语境」;08:193 键盘面仍排 Beta,本批只落 Esc 一项)。
🔴 **契约 3 · 设置与退出本体零重做、接线允许语境分支**:设置面本体与退出行为零改(F2/F3);**开合接线允许按语境分支**——现役 `main_overlay_flow.gd:11-13 _on_settings_closed` 带「回开始层」硬副作用(`start_overlay.visible = true`),营地语境须分支为「关设置回浮窗不回主菜单」。栈语义正解(F6/modal_stack.gd:21-41):浮窗登记于设置层**之下**(合同表槽位在营地层与 `settings_overlay` 之间,T0 与卡④ 对齐最终槽名),`ui.modals.flow.open(settings_overlay)` 时设置层绘制在浮窗之上遮挡之(open 只隐藏登记序在设置层**之上**的层,浮窗在下不被隐藏),`close` 后遮挡解除、浮窗自然重现——「关设置回浮窗」由栈语义保证;**语境分支点=`_on_settings_closed` 的 `start_overlay.visible = true` 硬副作用**(菜单路径保留、营地浮窗路径跳过),落 `main_overlay_flow.gd._on_settings_closed`(改)。`_quit_game`(`main.gd:143`)无语义副作用,原样复用。
🔴 **契约 3 · 设置与退出本体零重做、接线允许语境分支**:设置面本体与退出行为零改(F2/F3);**开合接线允许按语境分支**——现役 `main_overlay_flow.gd:11-13 _on_settings_closed` 带「回开始层」硬副作用(`start_overlay.visible = true`),营地语境须分支为「关设置回浮窗不回主菜单」。栈语义正解(F6/modal_stack.gd:21-41):浮窗登记于设置层**之下**(合同表槽位在营地层与 `settings_overlay` 之间,T0 与卡④ 对齐最终槽名),`ui.modals.flow.open(settings_overlay)` 时设置层绘制在浮窗之上遮挡之(open 只隐藏登记序在设置层**之上**的层,浮窗在下不被隐藏),`close` 后遮挡解除、浮窗自然重现——「关设置回浮窗」由栈语义保证;**语境分支点=`_on_settings_closed` 的 `start_overlay.visible = true` 硬副作用**(菜单路径保留、营地浮窗路径跳过),落 `main_overlay_flow.gd._on_settings_closed`(改)。**语境分支谓词(2026-11-01 双审钉死)=`camp_esc_overlay.visible`**(谁开的设置谁收:浮窗开态下关设置必回浮窗;菜单路径浮窗必未开);与 Esc 守卫谓词(契约 9①②,按 ModalStack 现开集合判)**分立**——禁复用同一谓词。`_quit_game`(`main.gd:143`)无语义副作用,原样复用。
🔴 **契约 4 · 返回主菜单动线**:营地场景屏隐藏+**选档层同收**(卡④ T3 口径)+开始层恢复;营地语境⇔闲时会话(无冒险会话——卡④ 契约 6「冒险进行中不开营地」+现役终局回菜单并 `_begin_session(false)` 收束推得),故动线本身不含会话收束;**回主菜单后菜单=卡②三钮、断点在档不在菜单露面**(禁实现者自作主张加「继续冒险」钮——那正是契约 1 在浮窗侧禁的);营地每笔消费即时写盘(`存档序列化.md`,行号 T0 复核),回主菜单不写档也无损。
🔴 **契约 4 · 返回主菜单动线**:**动线首步=close 浮窗**(`ui.modals.flow.close(camp_esc_overlay)`——浮窗是模态层,与屏壳可见性无关,缺此步浮窗残留主菜单上方;v1.4 补,V3 断言含浮窗 closed);营地场景屏隐藏+**选档层同收**(卡④ T3 口径)+开始层恢复;营地语境⇔闲时会话(无冒险会话——卡④ 契约 6「冒险进行中不开营地」+现役终局回菜单并 `_begin_session(false)` 收束推得),故动线本身不含会话收束;**回主菜单后菜单=卡②三钮、断点在档不在菜单露面**(禁实现者自作主张加「继续冒险」钮——那正是契约 1 在浮窗侧禁的);营地每笔消费即时写盘(`design/gdd/systems/表现与支撑/存档序列化.md`,行号 T0 复核),回主菜单不写档也无损。
🔴 **契约 5 · 顶栏 HUD 单口读数+刷新挂卡④单口**:钥石余额只经 MetaProgression/SaveManager 访问器单口读(`camp_shop_presenter.gd:62 camp_data()` 的 `keystone_balance()`;禁第二账本、禁视图自算);**刷新挂卡④定义的营地刷新单口**(卡④ v3.0 定名 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`,非已退役 `refresh_camp`——F9);刷新时机清单=购买后(挂单口)/进档或回营地进入时重读/夹具直写后探针显式触发(T2 落定)。
🔴 **契约 5 · 顶栏 HUD 单口读数+刷新挂卡④单口**:钥石余额只经 MetaProgression/SaveManager 访问器单口读(`camp_shop_presenter.gd:62 camp_data()` 的 `keystone_balance()`;禁第二账本、禁视图自算);**刷新挂卡④定义的营地刷新单口**(卡④ v3.1 定名 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`,非已退役 `refresh_camp`——F9);刷新时机清单=购买后(挂单口)/进档或回营地进入时重读/夹具直写后探针显式触发(T2 落定)。**「进档/回营地进入时重读」的触发主体(v1.4 钉死)=组合根进档动线尾(营地开层同口)显式调 `CampRefreshBus.refresh(camp_data())` 一闸**——禁 band `_ready` 自刷或自持第二入口(保 V6「显式触发才变」单口调用记录锚)。
🔴 **契约 6 · 零动效**:浮窗直开直关,子树零 AnimationPlayer、源码禁 `create_tween`/`UiKit.motion` 令牌(生涯页同款契约,F7);减动效设置项不新增(在役项沿用)。
🔴 **契约 7 · 层序纪律+遮挡结论(订正)**:浮窗入 `ui_registry.gd LAYER_CONTRACT`(改层序只改合同表;唯一落位口 `apply_layer_contract()`——F6);禁散落 `move_child`。**与调试角标遮挡的结论**:`debug_badge` 是末位 `float` 类、不注册 ModalStack,`ModalStack.open` 只隐藏登记层 ⇒ 浮窗开不波及角标节点(结构语义);但角标**发布态恒隐**(F10,`main.gd:98` `_debug_entry` 缺省 false),故「角标恒压浮窗且保持可点/可见」不是本批可跑断言面——删该运行期断言,以「浮窗登记序+open 只动登记层」结构断言承载,开发期临时启用角标后的浮窗不遮角标观感归用户目检。另写明一条栈语义防误触 L3:浮窗开会隐藏登记序在其上的已开层(如 debug_overlay),关闭恢复——属既有栈语义非新决策。
🔴 **契约 7 · 层序纪律+遮挡结论(订正)**:浮窗入 `ui_registry.gd LAYER_CONTRACT`(改层序只改合同表;唯一落位口 `apply_layer_contract()`——F6);禁散落 `move_child`。**与调试角标遮挡的结论**:`debug_badge` 是末位 `float` 类、不注册 ModalStack,`ModalStack.open` 只隐藏登记层 ⇒ 浮窗开不波及角标节点(结构语义);但角标**发布态恒隐**(F10,`main.gd:98` `_debug_entry` 缺省 false),故「角标恒压浮窗且保持可点/可见」不是本批可跑断言面——删该运行期断言,以「浮窗登记序+open 只动登记层」结构断言承载,开发期临时启用角标后的浮窗不遮角标观感归用户目检。另写明一条栈语义防误触 L3:浮窗开会隐藏登记序在其上的已开层(如 debug_overlay),关闭恢复——属既有栈语义非新决策。**浮窗句柄寻址(v1.5 钉死)=`ui.modals.camp.esc_overlay` 二层**(营地子容器,卡⑥ 建件;`ui.modals.flow.open/close` 调用形态不变——flow 是全局调度器、非句柄册成员)。
🔴 **契约 8 · provisional 口径留痕**:裁决 10「先按顶部小HUD栏设计」的「先」字留痕——HUD 栏形态按最小集落地(只承载钥石余额),扩项=新裁点。
🔴 **契约 9 · Esc 输入语义四态与接线定稿(v1.4 新立,2026-11-01 用户裁采纳双审推荐)**:Esc 路由按**四级优先序**,判据单点=ModalStack 登记册现开集合(禁手枚名单——设置面/ConfirmDialog/五子界面自然全被覆盖):
①**除浮窗外任一登记模态开态 → 无反馈**(「设置开在浮窗之上」态被本条覆盖=无反馈,关设置后浮窗自然重现——契约 3 栈语义;旧三态表「隔设置层误关不可见浮窗」歧义就此消解);
②**浮窗开态 → `close(camp_esc_overlay)`**(同键唤起/关闭单义切换,Q2 甲);
③**营地语境(`is_camp_context()`:营地场景屏 visible)→ `open(camp_esc_overlay)`**;
④**非营地语境 → 无反馈**(现役「选档语境 Esc 零响应」语义保持,契约 2)。
**接线定稿(同日裁,出 §9 白名单)**:事件驱动 `_unhandled_input`,键值判定 `KEY_ESCAPE`(配 physical_keycode 桌面惯例);**禁 Input 单例轮询**(`Viewport.push_input` 合成链不更新 `Input.is_action_pressed` 动作态——合成探针必然测不到轮询路径);**不挂 ui_cancel**(Beta 手柄面往 ui_cancel 挂键会致语义漂移;用户裁决原话「ESC键」;08:193 键盘面只加 KEY_ESCAPE 键值判定、不新增 InputMap action)。**触发沿=pressed 且非 echo**、忽略释放沿(真实键盘长按 echo 连发禁翻转浮窗;V1 补合成 echo 断言零响应)。
**拆分边界纪律适用性判定(req-card 第 15 节/code-conventions §1.1)**:本卡=**新增功能批**——新增 Esc 浮窗视图、营地顶栏 HUD 栏、Esc 键盘输入接线三类件,其余为存量文件接线改造;无「拆分存量单元/收敛/重构」动作,`camp_overlay_view.gd` 四柜台拆解退役属卡④ 职责(本卡不改该文件)。按 code-conventions §1.1 拆分判据五条,本卡不触发拆分/收敛/重构类,故不在设计契约区写拆分红线条款,仅在此写明判定理由。同理 req-card 第 13 节「禁为机检文本保留无效成员」(重构类卡必填)不适用;新增件类的「机检预算互斥=上游缺陷停卡退回写卡段」(第 9/14 节)已写入 §8 L3 与「实现规范预算」节。
## 4. 范围边界
@@ -86,12 +93,12 @@
| 文档 | 关联点 | 同步义务 |
|---|---|---|
| `design/gdd/08-Alpha版本标准.md`(v1.13) | :193「键盘 / 手柄输入(排 Beta)」行——Esc 营地浮窗已落地(用户明示裁决,澄清⑤裁决 9),须加注防「Beta 行=本批违例」误读 | 反哺点 · 列清单(行内加注) |
| `design/art/art-bible.md`(v3.24) | §6.2「钥石余额:常驻画面顶部的一行小 HUD 栏」(:396)呈现位从 CampOverlay 顶栏迁场景屏 HUD | 反哺点 · 列清单(权威面零直改,过用户裁决后归文档批) |
| `design/art/art-bible.md`(v3.31) | §6.2「钥石余额:常驻画面顶部的一行小 HUD 栏」(:396)呈现位从 CampOverlay 顶栏迁场景屏 HUD | 反哺点 · 列清单(权威面零直改,过用户裁决后归文档批) |
| `design/gdd/ux/camp.md`(v2.2) | §顶栏(:39-41)「钥石余额常驻顶部小 HUD 栏、刷新入口=营地刷新单口(卡⑤同口挂接)」实装状态回填 | 反哺点 · 列清单 |
| `待办清单.md`(仓库根) | 本卡登记条(五卡总条第 438-449 行)状态推进 | 反哺点 · 列清单(实现批禁写待办清单) |
| `待办清单.md`(仓库根) | 本卡登记条(五卡总条,:447 起——原引 438-449 行号漂移,v1.4 订正)状态推进 | 反哺点 · 列清单(实现批禁写待办清单) |
| `体验验证清单.md`(仓库根) | 新增目检条(Esc 浮窗三动线/HUD 余额观感/浮窗视觉面)——登记不阻塞 | 反哺点 · 列清单(实现批禁写体验验证清单,入册条目由文档批落) |
| `roadmap.md`(仓库根) | 迭代行编入/状态刷新 | 反哺点 · 列清单(实现批禁写 roadmap) |
| `game/tests/README.md` | 能力行入表(§7 对照表「能力行」列定名) | 写卡段定名(本卡只写卡内能力行文字,README 由主 Agent 统一收口);实现批只核对名实一致,禁写 |
| `game/tests/README.md` | 能力行入表(§7 对照表「能力行」列定名) | **已入表**(旅程段六,本卡两条新增能力行在役)——实现批只核对名实一致,禁写 |
| `game/presentation/*`(浮窗视图/HUD 栏/接线/注册面)、`game/probe/*` | 实现与断言本体 | 实施本体(可写) |
## 卡前·文件结构与职责(T0 前)
@@ -101,11 +108,14 @@
| 文件 | 职责 | 改动类型 | 落位 | 对照表行 |
|---|---|---|---|---|
| `game/presentation/views/camp_esc_overlay.gd` | Esc 浮窗视图:`UiKit.modal_shell` 模态壳+三钮(返回主菜单/设置/退出游戏,退出用 `UiKit.danger_button`)+Q2 再按 Esc 关窗;零新素材 | 行为改动 | 新建 | V1/V2/V5/B1 |
| `game/presentation/views/camp_hud_band.gd` | 营地顶栏小 HUD 栏:承载 `UiKit.keystone_badge`(钥石余额单口读数);订阅卡④ `CampRefreshBus.refresh()` 刷新 | 行为改动 | 新建 | V6/B2 |
| `game/presentation/views/camp_hud_band.gd` | 营地顶栏小 HUD 栏:承载 `UiKit.keystone_badge`(钥石余额单口读数);挂接卡④ `CampRefreshBus.refresh()` 刷新 | 行为改动 | 新建 | V6/B2 |
| `game/presentation/main_esc_flow.gd` | 组合根 Esc 流程承接件:键盘输入路由+营地语境判据+Q2 再按关窗+三出口动线(返回主菜单/设置/退出);静态承接件,main 侧留同名薄委托 | 行为改动 | 新建 | V1/V3/B1/B3 |
| `game/presentation/main.gd` | 组合根:Esc 输入入口+`main_esc_flow.gd` 同名薄委托+返回主菜单动线接线 | 行为改动 | 复用既有(改) | V1/V3/B3 |
| `game/presentation/ui_registry.gd` | `LAYER_CONTRACT` 加 `camp_esc_overlay` 槽(登记于营地层与 `settings_overlay` 之间,契约 3 正解)+`node()` 寻址 | 行为改动 | 复用既有(改) | V8/B1 |
| `game/presentation/main_overlay_flow.gd` | `_on_settings_closed` 语境分支:营地浮窗路径关设置回浮窗、不回主菜单(契约 3) | 行为改动 | 复用既有(改) | V4/B3 |
| `game/presentation/ui_registry.gd` | `LAYER_CONTRACT` 加 `camp_esc_overlay` 槽(登记于营地层与 `settings_overlay` 之间,契约 3 正解)+`node()` 寻址(`camp.*` 二层解析由卡⑥ 建件承担,本卡只消费) | 行为改动 | 复用既有(改) | V8/B1 |
| `game/presentation/main_overlay_flow.gd` | `_on_settings_closed` 语境分支:营地浮窗路径关设置回浮窗、不回主菜单(契约 3;谓词=camp_esc_overlay.visible) | 行为改动 | 复用既有(改) | V4/B3 |
| `game/presentation/ui_modals.gd` | 本卡零句柄新增(浮窗句柄落 `ui_camp_modals.gd` 子容器——卡⑥ 前置建件,2026-11-01 用户裁);类变量 14 预期不升(S13 触面消除,见「实现规范预算·存量触面一查」) | 非行为改动(卡⑤ 段零改) | 复用既有 | V8/B1 |
| `game/presentation/ui_camp_modals.gd`(卡⑥ 新建) | 营地域句柄子容器:本卡 T1 落第六枚句柄 `esc_overlay`(寻址 `ui.modals.camp.esc_overlay` 二层;卡④ T3 已落五枚) | 行为改动 | 卡⑥ 新建(跨卡共改) | V8/B1 |
| `game/presentation/main_ui_build.gd` | 浮窗实例化装配点:`.new()`+装配尾 `apply_layer_contract(root)`(先例 :105/:121/:242;S13 触面) | 行为改动 | 复用既有(改) | V8/B1 |
| `game/presentation/views/camp_scene_view.gd`(卡④ 新建) | 顶栏预留位 `set_hud_band()` 填 `CampHudBand` 实例(卡④ T2 跨卡接口①「卡⑤ 填值」);本卡只填值、不改场景图铺底/命中区逻辑 | 行为改动 | 卡④ 新建(跨卡共改,仅顶栏填值) | V6/B2 |
| `game/probe/screen_flow_probe.gd` | 探针宿主:扩展现 :153-165 Esc 合成段为营地语境浮窗断言+三动线+Q2 关窗+HUD 余额读数;与卡④ T5 营地键段按行域分界(§0) | 非行为改动(测试面) | 复用既有(改) | V1/V3/V4/V6 |
| `game/probe/ui_struct_probe.gd` | 浮窗三钮结构断言+零动效契约段(仿 career :379-391)+顶栏位/登记序结构断言 | 非行为改动(测试面) | 复用既有(改) | V2/V7/V8 |
@@ -120,8 +130,8 @@
| 新件 | 复用面(grep 同职责实现) | 唯一面(grep 同名同义标识符) | 分层与交互面(调用路径+跨层出口) |
|---|---|---|---|
| `camp_esc_overlay.gd` | 盘上视图件=`camp_overlay_view.gd`(CampOverlay 四柜台,随卡④ 形态退役、反参照禁复用)、`settings_overlay.gd`/`career_overlay.gd`(各有专职、无三钮 Esc 浮窗同职责实现)、`ui_kit.gd modal_shell`(工厂只产 dim+center+panel+VBox、不产完整浮窗)。grep `camp_esc`/`esc_overlay` 盘上无同职责实现 → 不能复用,须新建视图件;模态壳/按钮复用 `UiKit.modal_shell`(`ui_kit.gd:487`)/`UiKit.danger_button`(:700),零新素材零新色值 | grep `camp_esc_overlay`/`EscOverlay` 盘上无第二处;三钮文案「返回主菜单/设置/退出游戏」只此一处定义,禁在探针/T 卡各写一份 | 呈现层视图,零 Core 写调用;三钮 `pressed` → 组合根 `main_esc_flow.gd` → 现役动线(`_quit_game`/`ui.modals.flow.open(settings_overlay)`/返回主菜单);Q2 再按 Esc 关窗由组合根路由,视图只发 `closed` 信号 |
| `camp_hud_band.gd` | 盘上钥石徽消费面四处(F4),但**无「顶栏 HUD 栏」独立视图件**——现钥石徽直接挂 CampOverlay 顶栏(`camp_overlay_view.gd:125`),随卡④ 拆解退役;新 HUD 栏须独立承载读数+订阅刷新单口,职责不同 → 不能复用,须新建 | grep `camp_hud_band`/`CampHudBand`/`hud_band` 盘上无第二处;余额读数只经 `camp_data()` 单口(F9),禁视图自算 | 呈现层视图;`UiKit.keystone_badge`(`ui_kit.gd:633`)承载显示;订阅卡④ `CampRefreshBus.refresh(camp_data)` 单口刷新(跨卡接口②);经卡④ `camp_scene_view.gd set_hud_band()` 顶栏预留位挂入(跨卡接口①);零 Core 写调用 |
| `camp_esc_overlay.gd` | 盘上视图件=`camp_overlay_view.gd`(CampOverlay 四柜台,随卡④ 形态退役、反参照禁复用)、`settings_overlay.gd`/`career_overlay.gd`(各有专职、无三钮 Esc 浮窗同职责实现)、`ui_kit.gd modal_shell`(工厂只产 dim+center+panel+VBox、不产完整浮窗)。grep `camp_esc`/`esc_overlay` 盘上无同职责实现 → 不能复用,须新建视图件;模态壳/按钮复用 `UiKit.modal_shell`(`ui_kit.gd:486`)/`UiKit.danger_button`(:699),零新素材零新色值 | grep `camp_esc_overlay`/`EscOverlay` 盘上无第二处;三钮文案「返回主菜单/设置/退出游戏」只此一处定义,禁在探针/T 卡各写一份 | 呈现层视图,零 Core 写调用;三钮 `pressed` → 组合根 `main_esc_flow.gd` → 现役动线(`_quit_game`/`ui.modals.flow.open(settings_overlay)`/返回主菜单);Q2 再按 Esc 关窗由组合根路由,视图只发 `closed` 信号 |
| `camp_hud_band.gd` | 盘上钥石徽消费面四处(F4),但**无「顶栏 HUD 栏」独立视图件**——现钥石徽直接挂 CampOverlay 顶栏(`camp_overlay_view.gd:125`),随卡④ 拆解退役;新 HUD 栏须独立承载读数+挂接刷新单口(接收推送,非信号订阅),职责不同 → 不能复用,须新建 | grep `camp_hud_band`/`CampHudBand`/`hud_band` 盘上无第二处;余额读数只经 `camp_data()` 单口(F9),禁视图自算 | 呈现层视图;`UiKit.keystone_badge`(`ui_kit.gd:632`)承载显示;挂接卡④ `CampRefreshBus.refresh(camp_data)` 单口刷新(跨卡接口②);经卡④ `camp_scene_view.gd set_hud_band()` 顶栏预留位挂入(跨卡接口①);零 Core 写调用 |
| `main_esc_flow.gd` | 盘上组合根承接件=`main_overlay_flow.gd`(设置/状态/生涯/选档/图例/钱包六类覆盖层开合与数据投喂)、`main_adventure_flow.gd`、`main_debug_flow.gd`。**无「键盘输入路由+Esc 三出口动线」同职责实现**;并入 `main_overlay_flow.gd` 会使其承载第二变化原因(开合数据投喂 vs 键盘输入交互),且该文件已 6 类开合 → 不能复用,须新建单一职责承接件;`main.gd` 留同名薄委托(与现行拆件模式一致,探针 `main.X` 路径零改动) | grep `main_esc_flow`/`_on_esc`/`EscFlow` 盘上无第二处;营地语境判据只此一处定义(营地场景屏 visible 且无营地子模态开态),禁散落 | 组合根拆件(静态、零成员态,仿 `main_overlay_flow.gd`);键盘输入 → 营地语境判据 → 浮窗 `ui.modals.flow.open/close`(模态栈单口);三出口 → 现役动线;返回主菜单 → 营地隐+选档层同收+开始层恢复(卡④ T3 同口径);Core 零 UI 不破 |
### 非新增件说明
@@ -145,7 +155,7 @@
### 存量触面一查
本卡触碰存量文件:`main.gd`/`ui_registry.gd`/`main_overlay_flow.gd`/`camp_scene_view.gd`(卡④ 新建)/`screen_flow_probe.gd`/`ui_struct_probe.gd`。**T0 读数步骤写明「S13 键较 `tools/gdstyle_baseline.txt` 登记值只降不升」的预期与达成手段**:`main.gd` 组合根新增 Esc 输入入口与薄委托(不引入重复 load/循环内 await/循环内分配等 S13 违规类型,键只降不升);`ui_registry.gd` 合同表加一槽、`node()` 加一寻址分支(不新增公开方法面);`main_overlay_flow.gd` `_on_settings_closed` 加语境分支(单函数分支数仍 ≤8、返回 ≤6);`camp_scene_view.gd` 只填 `set_hud_band()` 顶栏位;探针改接=加断言段、不新增扫描面键。任何 S13 键被迫上升=§8 L3 停卡。**`main.gd` 为组合根架构先例,其公开方法基数不在本卡新件三查面内**(第 14 节三查只对新增文件)。
本卡触碰存量文件:`main.gd`/`ui_registry.gd`/`ui_modals.gd`/`main_ui_build.gd`/`main_overlay_flow.gd`/`camp_scene_view.gd`(卡④ 新建)/`screen_flow_probe.gd`/`ui_struct_probe.gd`。**T0 读数步骤写明「S13 键较 `tools/gdstyle_baseline.txt` 登记值只降不升」的预期与达成手段**:`main.gd` 组合根新增 Esc 输入入口与薄委托(不引入重复 load/循环内 await/循环内分配等 S13 违规类型,键只降不升);`ui_registry.gd` 合同表加一槽、`node()` 加一寻址分支(不新增公开方法面);`ui_modals.gd` 零句柄新增(浮窗句柄落 `ui_camp_modals.gd` 子容器,类变量 14 预期——v1.4「14→15 恰贴」句作废:该算术只算本卡 +1、未算卡④ 同批净+4,两卡合计 19>15,2026-11-01 停机呈报裁决改子容器);`ui_camp_modals.gd`(卡⑥ 建件)本卡落第六枚句柄(类变量 5→6);`main_ui_build.gd` 加浮窗实例化与装配(方法面微增,T0 实测对账);`main_overlay_flow.gd` `_on_settings_closed` 加语境分支(单函数分支数仍 ≤8、返回 ≤6);`camp_scene_view.gd` 只填 `set_hud_band()` 顶栏位;探针改接=加断言段、不新增扫描面键。任何 S13 键被迫上升=§8 L3 停卡。**`main.gd` 为组合根架构先例,其公开方法基数不在本卡新件三查面内**(第 14 节三查只对新增文件)。
### 指标例外通道
@@ -157,32 +167,32 @@
### T0 事实坐实与跨卡对齐(≈0.5–1d)
- 取用:待办清单五卡总条实况(卡④ 已重立 v3.0、卡②③ 迭代 33 合回)、§0 跨卡接口、卡④ v3.0 §0.5/T2/T3、`game/tests/README.md` 能力表。
- 取用:待办清单五卡总条实况(卡④ v3.1 现行、卡②③ 迭代 33 合回)、§0 跨卡接口、卡④ v3.1 §0.5/T2/T3、`game/tests/README.md` 能力表。
- [ ] 复核 §2 F1–F10 全部 `file:line` 行号;漂移就地订正、订正不了的写明「开工日 T0 复核」,不硬编旧行号。
- [ ] 与卡④ 对齐三项接口:①顶栏预留位 `camp_scene_view.gd set_hud_band()` 填值签名;②营地刷新单口 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`;③终局落点不变量(现役=终局 continue 回主菜单,若改落点为营地则本卡返回动线补 `_begin_session(false)`)——接口名保持既有,不改名;与卡④ 确认 `screen_flow_probe.gd` 行域分界(§0)。
- [ ] **Esc 输入合成先例复核**:读 `game/probe/screen_flow_probe.gd:153-165`(`InputEventKey`+`Viewport.push_input` 合成 Esc、断言选档语境零响应),确认该机制可迁移到营地语境;与 §9 第 1 条接线方式选型联动,T0 定选型并回写文件结构图接线落点。
- [ ] **Esc 输入合成先例复核**:读 `game/probe/screen_flow_probe.gd:153-165`(`InputEventKey`+`Viewport.push_input` 合成 Esc、断言选档语境零响应),确认该机制可迁移到营地语境;接线方式已定稿(契约 9——事件驱动 `_unhandled_input`+`KEY_ESCAPE`,2026-11-01 裁),T0 复核合成链可达 unhandled 段(push_input 不更新 Input 单例动作态——禁轮询路线的实据)并回写文件结构图接线落点。
- [ ] Q1/Q2/Q3 裁定执行确认(甲/甲/甲,见 §10);开工门核:U0 发令、grilling 复核门过、卡④ 代码波状态。
- [ ] 跑 `bash tools/gdstyle.sh game` 开发期局部读数,记录三新件预算实数与存量触面 S13 基线键(预期只降不升);本步最窄确认,不构成验收。
- [ ] 跑 `bash tools/static_scan.sh` 开发期局部读数(gdstyle 键=S13,闸件 `tools/gdstyle_gate.py`——原 `tools/gdstyle.sh` 不存在,v1.4 订正),记录三新件预算实数与存量触面 S13 基线键(预期只降不升);本步最窄确认,不构成验收。
- 产出:基线 commit、三新件接口块定稿(签名/取值)、S13 读数、跨卡接口核对表、Esc 合成先例复核结论;后续卡只按本卡契约施工。
### T1 Esc 浮窗视图与键盘接线(≈1–1.5d)
- 取用:T0 三新件接口块、Esc 合成先例复核结论、`ui_kit.gd:487 modal_shell`/:700 `danger_button`/:633 `keystone_badge`、`ui_registry.gd:15-35 LAYER_CONTRACT`。
- [ ] 在 `camp_esc_overlay.gd` 实现 `setup_buttons()`:`UiKit.modal_shell`(`ui_kit.gd:487`,只产 dim+center+panel+VBox、无内置关闭件)+三钮(返回主菜单/设置/退出游戏,退出用 `UiKit.danger_button` :700);零新素材零新色值。
- 取用:T0 三新件接口块、Esc 合成先例复核结论、`ui_kit.gd:486 modal_shell`/:699 `danger_button`/:632 `keystone_badge`、`ui_registry.gd:15-35 LAYER_CONTRACT`。
- [ ] 在 `camp_esc_overlay.gd` 实现 `setup_buttons()`:`UiKit.modal_shell`(`ui_kit.gd:486`,只产 dim+center+panel+VBox、无内置关闭件)+三钮(返回主菜单/设置/退出游戏,退出用 `UiKit.danger_button` :699);零新素材零新色值。
- [ ] 在 `camp_esc_overlay.gd` 实现 `set_close_path()`/`bind_signals()`:Q2 裁甲=再按 Esc 关窗(同键唤起/关闭单义切换),关闭由组合根路由、视图只发 `closed` 信号;三钮 `pressed` 外发不内含动线。
- [ ] 在 `main_esc_flow.gd` 实现 `is_camp_context() -> bool`:判据=营地场景屏 visible(根态与子界面开态下皆真——子界面是模态叠加非熄屏;复用 `screen_flow_probe.gd:54` 营地旗标先例,卡④ T5 改指新场景根句柄后对齐)且无营地子模态开态(Q1 裁甲);调试浮窗开态不影响判据(全局浮层、营地屏仍可见)。
- [ ] 在 `main_esc_flow.gd` 实现 `on_esc_input(ev: InputEvent) -> void`/`open_overlay() -> void`/`close_overlay() -> void`:营地语境 Esc → `ui.modals.flow.open(camp_esc_overlay)`;再按 Esc → `close`;非营地语境(boot 菜单)不唤起(契约 2)。接线方式按 T0 选型,禁吞他处输入。
- [ ] 在 `ui_registry.gd` `LAYER_CONTRACT` 加 `camp_esc_overlay` 槽(位于营地层与 `settings_overlay` 之间,契约 3 登记序正解)+`node()` 寻址;禁散落 `move_child`(F6)。
- [ ] 在 `main.gd` 加 Esc 输入入口与 `main_esc_flow.gd` 同名薄委托(探针 `main.X` 路径零改动)。
- [ ] 在 `main_esc_flow.gd` 实现 `is_camp_context() -> bool`:判据=营地场景屏 visible(子界面是模态叠加非熄屏;复用 `screen_flow_probe.gd:54` 营地旗标先例——:53 是菜单行、:54 才是营地旗标,v1.4 订正);「子界面开态不唤浮窗」(Q1 甲)由契约 9① 现开集合判据承载,不并入本谓词;调试浮窗开态不影响判据(全局浮层、营地屏仍可见)。
- [ ] 在 `main_esc_flow.gd` 实现 `on_esc_input(ev: InputEvent) -> void`/`open_overlay() -> void`/`close_overlay() -> void`:按**契约 9 四态优先序**路由(①除浮窗外任一登记模态开→无反馈②浮窗开→close③营地语境→open④非营地语境→无反馈),判据单点=ModalStack 登记册现开集合、禁手枚名单;接线定稿=事件驱动 `_unhandled_input`+`KEY_ESCAPE`(physical_keycode)、禁 Input 单例轮询、不挂 ui_cancel、pressed 且非 echo(契约 9 接线定稿 2026-11-01 已裁,不再 T0 选型)。
- [ ] 在 `ui_registry.gd` `LAYER_CONTRACT` 加 `camp_esc_overlay` 槽(位于营地层与 `settings_overlay` 之间,契约 3 登记序正解)+`node()` 寻址;句柄落 `ui_camp_modals.gd` 子容器第六枚 `esc_overlay`(卡⑥ 前置建件,寻址 `ui.modals.camp.esc_overlay` 二层)——禁平铺回 `ui_modals.gd`(S13 上限,2026-11-01 用户裁);禁散落 `move_child`(F6)。
- [ ] 在 `main.gd` 加 Esc 输入入口(`_unhandled_input`)与 `main_esc_flow.gd` 同名薄委托(探针 `main.X` 路径零改动);`main.gd` 的 `_unhandled_input` 将是产品码首个键盘入口——`main_esc_flow.gd` 头注自陈此身份(2026-11-01 双审注)。
- [ ] 零动效(契约 6):浮窗子树零 AnimationPlayer、源码禁 `create_tween`/`UiKit.motion` 令牌(仿 `ui_struct_probe.gd:379-391`)。
- 产出:`CampEscOverlay.setup_buttons()`/`set_close_path()`/`bind_signals()`、`closed` 信号;`MainEscFlow.on_esc_input()`/`is_camp_context()`/`open_overlay()`/`close_overlay()`;`LAYER_CONTRACT` 新槽键 `camp_esc_overlay`;T2 按接口块接线。
### T2 营地顶栏小 HUD 栏(≈0.5–1d)
- 取用:T1 `MainEscFlow`、`camp_hud_band.gd` 接口块、卡④ `CampRefreshBus.refresh(camp_data)` 单口、`camp_scene_view.gd set_hud_band()` 顶栏预留位、`camp_shop_presenter.gd:62 camp_data()` 读数单点。
- [ ] 在 `camp_hud_band.gd` 实现 `set_balance(v: int) -> void`/`bind_refresh_bus(bus: Object) -> void`/`refresh(camp_data: Dictionary) -> void`:`UiKit.keystone_badge`(`ui_kit.gd:633`)承载,读数只经 `camp_data()` 单口(F9/契约 5,禁第二账本、禁视图自算);订阅卡④ `CampRefreshBus.refresh(camp_data)`。
- [ ] 在 `camp_hud_band.gd` 实现 `set_balance(v: int) -> void`/`bind_refresh_bus(bus: Object) -> void`/`refresh(camp_data: Dictionary) -> void`:`UiKit.keystone_badge`(`ui_kit.gd:632`)承载,读数只经 `camp_data()` 单口(F9/契约 5,禁第二账本、禁视图自算);挂接卡④ `CampRefreshBus.refresh(camp_data)`;载体循 UiKit `page_shell` 的 `PRESET_TOP_WIDE`+`custom_minimum_size` 先例(ui_kit.gd:523-525),禁手动 rect。
- [ ] 在卡④ `camp_scene_view.gd` 的 `set_hud_band()` 顶栏预留位填 `CampHudBand` 实例(高 48px 浮层覆盖、不挤压画面;卡④ T2 跨卡接口①);`camp_scene_view.gd` 场景图铺底/命中区逻辑零改(卡④ 职责)。
- [ ] 刷新时机清单落定(契约 5):①购买后(挂单口)②进档/回营地进入时重读③夹具直写后探针显式触发——断言前须显式触发,禁依赖「自动重读」假设。
- [ ] 刷新时机清单落定(契约 5):①购买后(挂单口)②进档/回营地进入时重读③夹具直写后探针显式触发——断言前须显式触发,禁依赖「自动重读」假设;②的触发主体=组合根进档动线尾(营地开层同口)显式调 `CampRefreshBus.refresh(camp_data())` 一闸(契约 5 v1.4 补钉),禁 band `_ready` 自刷或自持第二入口。
- [ ] HUD 栏形态按最小集(契约 8):只承载钥石余额;扩项=新裁点。
- [ ] camp 截图/golden 联动:HUD 落位后核 camp 截图漂移热区→限 HUD 区显式更新+呈报;现无 camp 逐像素 golden(卡④ 契约 10),camp 验收=shot_probe 出图+shot_checker 语义断言。
- 产出:`CampHudBand.set_balance()`/`bind_refresh_bus()`/`refresh()`、顶栏填值、刷新时机清单;T3 按接口块验收。
@@ -190,10 +200,10 @@
### T3 三出口动线、设置语境分支与断言批(≈1–1.5d)
- 取用:T1/T2 产物、`main_overlay_flow.gd:11-13 _on_settings_closed`、`main.gd:143 _quit_game`、`camp_shop_presenter.gd:50-53 on_camp_closed`、`screen_flow_probe.gd:153-165` Esc 合成段、`ui_struct_probe.gd:379-391` 零动效段。
- [ ] 在 `main_esc_flow.gd` 实现 `_on_return_to_menu() -> void`:营地场景屏隐藏+选档层同收+开始层恢复(卡④ T3 动线同口);营地语境⇔闲时会话(契约 4 前提),动线不含会话收束;回主菜单后菜单=卡②三钮、禁加「继续冒险」钮。
- [ ] 在 `main_esc_flow.gd` 实现 `_on_return_to_menu() -> void`:**首步 close 浮窗**(`ui.modals.flow.close(camp_esc_overlay)`——浮窗是模态层,屏壳可见性收不了它,缺此步浮窗残留主菜单上方;契约 4 v1.4 补)→ 营地场景屏隐藏+选档层同收+开始层恢复(卡④ T3 动线同口);营地语境⇔闲时会话(契约 4 前提),动线不含会话收束;回主菜单后菜单=卡②三钮、禁加「继续冒险」钮。
- [ ] 在 `main_esc_flow.gd` 实现 `_on_settings() -> void`/`_on_quit() -> void`:设置→`ui.modals.flow.open(settings_overlay)`(浮窗登记于其下、关后遮挡解除重现);退出→`_quit_game`(`main.gd:143`,原样复用)。
- [ ] 在 `main_overlay_flow.gd` `_on_settings_closed` 加语境分支:营地浮窗路径关设置回浮窗、不回主菜单(契约 3;现役 :13 `start_overlay.visible = true` 硬副作用仅菜单路径保留)。
- [ ] 探针(宿主 `screen_flow_probe.gd`,已在 `tools/probe_gate.sh:142-146` 登记、零登记成本;不另起新文件):扩展 :153-165 Esc 合成段——营地根态合成 Esc→浮窗可见+三项在位;营地子界面开态合成 Esc→浮窗不唤起(Q1 甲案判据);再按 Esc 关窗;非营地语境(boot 菜单)不唤起(契约 2);返回主菜单动线(营地隐+选档层收+菜单显);设置项开合(含营地浮窗→设置→关闭回浮窗高危路径+「关闭设置后禁弹主菜单」反向断言);退出项接线断言(headless 不可真退)。
- [ ] 探针(宿主 `screen_flow_probe.gd`,已在 `tools/probe_gate.sh:142-146` 登记、零登记成本;不另起新文件):扩展 :153-165 Esc 合成段——营地根态合成 Esc→浮窗可见+三项在位;营地子界面开态合成 Esc→无反馈(Q1 甲,契约 9①);**设置开在浮窗之上合成 Esc→无反馈且浮窗不误关**(契约 9①——浮窗不隔层关闭,关设置后浮窗重现);再按 Esc 关窗(契约 9②);非营地语境(boot 菜单)不唤起(契约 9④);**冒险语境合成 Esc→浮窗不现**(契约 2 全局接线回归面,screen_flow_probe 冒险步内);**echo 事件合成→零响应**(触发沿 pressed 且非 echo,契约 9);返回主菜单动线(**浮窗自收**+营地隐+选档层收+菜单显);设置项开合(含营地浮窗→设置→关闭回浮窗高危路径+「关闭设置后禁弹主菜单」反向断言);退出项接线断言(headless 不可真退)。
- [ ] HUD 余额断言:读数=存档单口(夹具经**隔离存档实例直写** `meta_state._add_keystone(9999)` 先例 `game/probe/ui_click_probe.gd:74`;**不经 DebugOverlay 界面——其钥石项是只读投影** `debug_overlay.gd:194-205`);直写后显式触发刷新断言。
- [ ] 结构断言(`ui_struct_probe.gd`):浮窗三钮在位且仅三;零动效(仿 career :379-391);`LAYER_CONTRACT` 登记序(浮窗入合同表、`system_test.gd:94-106` V1c 树序链复跑);**浮窗开不波及 `debug_badge` 节点**(F10:发布态恒隐,改结构断言替代 v1.2 `visible==true` 断言)。
- [ ] 正向验收断言(v1.2 计划的人工制造红取证条目整条已删,替换为 §7 上表正向断言):每条从真实入口进入、判据绑被测改动特有的可观测提交面(信号/入参/状态源取值/可见性/树序),在当轮真实跑批执行并通过;**禁把生产码改成错值看断言抓不抓得到、禁回退 hunk 看断言红不红**(人为制造红属伪造证据);判据绑不上=静态判据+评审注记申报观测面局限,证明做不出=停卡呈报。
@@ -215,22 +225,22 @@
## 7. 验证闸:改动—验收对照表
七格表:需求点|改动(文件:行/行为)|验收测试(名称+落点+尾缀)|层级(单元/接口/集成/探针)|真实入口|判据绑定面|能力行。**每个需求点至少一行;每个行为改动至少一行(V1–V10 需求点行+B1–B3 行为改动专行);每行必须有真实入口和判据绑定面——缺任一项=该需求点未验收,卡片不成立。** 断言尾缀〔ESC批 Vn〕/〔ESC批 Bn〕,与卡片清单互相 grep。能力行格式 `既有:<能力名>;新增:<能力名>`(中文分号分隔,对齐机检与卡④ v3.0 先例),两形态名称都须在 `game/tests/README.md` 能力表在场(既有=现役在场名,新增=本卡定名、由主 Agent 统一收口入表;本卡只写卡内能力行文字,README 由主 Agent 收口)。
七格表:需求点|改动(文件:行/行为)|验收测试(名称+落点+尾缀)|层级(单元/接口/集成/探针)|真实入口|判据绑定面|能力行。**每个需求点至少一行;每个行为改动至少一行(V1–V10 需求点行+B1–B3 行为改动专行);每行必须有真实入口和判据绑定面——缺任一项=该需求点未验收,卡片不成立。** 断言尾缀〔ESC批 Vn〕/〔ESC批 Bn〕,与卡片清单互相 grep。能力行格式 `既有:<能力名>;新增:<能力名>`(中文分号分隔,对齐机检与卡④ v3.1 先例),两形态名称都须在 `game/tests/README.md` 能力表在场(既有=现役在场名,新增=本卡定名、由主 Agent 统一收口入表;本卡只写卡内能力行文字,README 由主 Agent 收口)。
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|
| V1 Esc 唤起(根态/子界面/关窗/非营地) | `camp_esc_overlay.gd` 浮窗视图;`main_esc_flow.gd on_esc_input()`/`is_camp_context()`/`open_overlay()`/`close_overlay()`;`main.gd` 输入入口 | `screen_flow_probe.gd:_ready`(Esc 唤起/子界面开态不唤起/再按关窗/非营地不唤起段)〔ESC批 V1〕 | 探针 | 营地场景根态合成 Esc 键事件(`InputEventKey`+`Viewport.push_input`,先例 :156-160)→ `main_esc_flow.on_esc_input()` → `ui.modals.flow.open/close` | 根态 Esc→浮窗 `visible=true`;再按→`visible=false`(绑 open/close 调用记录+浮窗可见性);营地子界面开态合成 Esc→浮窗 `visible=false`(Q1 甲案,绑 `is_camp_context()` 无子模态开态判据);boot 菜单合成 Esc→浮窗 `visible=false`(契约 2,绑语境判据返回值) | 新增:Esc 浮窗唤起与三出口动线 |
| V1 Esc 唤起(根态/子界面/关窗/非营地) | `camp_esc_overlay.gd` 浮窗视图;`main_esc_flow.gd on_esc_input()`/`is_camp_context()`/`open_overlay()`/`close_overlay()`;`main.gd` 输入入口 | `screen_flow_probe.gd:_ready`(Esc 唤起/子界面开态不唤起/再按关窗/非营地不唤起段)〔ESC批 V1〕 | 探针 | 营地场景根态合成 Esc 键事件(`InputEventKey`+`Viewport.push_input`,先例 :156-160)→ `main_esc_flow.on_esc_input()` → `ui.modals.flow.open/close` | 根态 Esc→浮窗 `visible=true`;再按→`visible=false`(绑 open/close 调用记录+浮窗可见性);子界面开态/设置开在浮窗之上合成 Esc→无反馈且浮窗不误关(契约 9①,浮窗不隔层关闭);boot 菜单与冒险语境合成 Esc→浮窗不现(契约 9④/契约 2,绑语境判据返回值);echo 合成→零响应(触发沿 pressed 且非 echo,契约 9) | 新增:Esc 浮窗唤起与三出口动线 |
| V2 浮窗三项 | `camp_esc_overlay.gd setup_buttons()` 三钮(modal_shell+danger_button) | `ui_struct_probe.gd:_ready`(浮窗三钮结构段)〔ESC批 V2〕 | 探针 | 浮窗视图实例化 → 三钮节点树 | 三钮节点存在且仅三(绑按钮名与数量;禁第四钮) | 既有:UI 全局主题与按钮五态(含对比度锚);新增:Esc 浮窗唤起与三出口动线 |
| V3 返回主菜单 | `main_esc_flow.gd _on_return_to_menu()`(营地隐+选档层同收+开始层恢复) | `screen_flow_probe.gd:_ready`(返回动线段)〔ESC批 V3〕 | 探针 | 浮窗「返回主菜单」钮 `pressed` → `main_esc_flow._on_return_to_menu()` → 屏显编排 | 营地场景屏 `visible=false`+选档层 `visible=false`+`start_overlay.visible=true`(绑三处可见性;`_screen_seals()` :51-57 互斥复跑) | 新增:Esc 浮窗唤起与三出口动线 |
| V4 设置项(双路径) | `main_esc_flow.gd _on_settings()`;`main_overlay_flow.gd _on_settings_closed` 语境分支 | `screen_flow_probe.gd:_ready`(营地浮窗→设置→关闭回浮窗段)〔ESC批 V4〕+菜单路径冒烟 V3g settings 段复跑 | 探针(+冒烟复跑) | 浮窗「设置」钮 → `ui.modals.flow.open(settings_overlay)` → 关闭 → `_on_settings_closed` 语境分支 | 设置开→浮窗被遮挡不可见(绑 ModalStack 遮挡记账);关闭→浮窗 `visible=true`+`start_overlay.visible=false`(绑语境分支;反向断言「关闭设置后禁弹主菜单」) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| V5 退出项 | `main_esc_flow.gd _on_quit()` → `main.gd:143 _quit_game()`(原样复用) | `ui_struct_probe.gd:_ready`(退出钮接线结构断言)〔ESC批 V5〕 | 探针(源码/结构,headless 不可真退) | 浮窗「退出游戏」钮 `pressed` → `_on_quit()` → `_quit_game()`(`settings_store.save_settings()`+`get_tree().quit()`) | 钮 `pressed` 连接目标=`_on_quit`→`_quit_game`(绑信号连接与调用链;headless 不真退=静态判据+评审注记申报观测面局限) | 新增:Esc 浮窗唤起与三出口动线 |
| V6 HUD 余额 | `camp_hud_band.gd set_balance()`/`bind_refresh_bus()`/`refresh()`;`camp_scene_view.gd set_hud_band()` 填值;订阅卡④ `CampRefreshBus.refresh()` | `screen_flow_probe.gd:_ready`(HUD 余额读数/刷新段)〔ESC批 V6〕+`ui_struct_probe.gd:_ready`(顶栏位结构段) | 探针(跨卡联验:购买后刷新挂卡④单口) | 隔离存档实例 `meta_state._add_keystone(9999)`(`ui_click_probe.gd:74` 先例)→ 显式触发 `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` 读数 | `keystone_badge.value_label.text`=`camp_data()["balance"]`(`keystone_balance()` 单口取值);直写后显式触发刷新才变(绑单口调用记录+badge 文本状态源) | 既有:元进度入账与锻造两轴;新增:营地顶栏 HUD 钥石读数与刷新单口同步 |
| V7 零动效 | `camp_esc_overlay.gd` 直开直关(零 AnimationPlayer、源码禁 `create_tween`/`UiKit.motion`) | `ui_struct_probe.gd:_ready`(浮窗零动效契约段,仿 career :379-391)〔ESC批 V7〕 | 探针(结构/源码扫描) | 浮窗视图子树+`camp_esc_overlay.gd` 源码 | 子树零 AnimationPlayer+源码零 `create_tween`/`UiKit.motion` 令牌(扫描面 >0 非空前置) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| V3 返回主菜单 | `main_esc_flow.gd _on_return_to_menu()`(营地隐+选档层同收+开始层恢复) | `screen_flow_probe.gd:_ready`(返回动线段)〔ESC批 V3〕 | 探针 | 浮窗「返回主菜单」钮 `pressed` → `main_esc_flow._on_return_to_menu()` → 屏显编排 | **浮窗 closed(动线首步自收,契约 4 v1.4)**+营地场景屏 `visible=false`+选档层 `visible=false`+`start_overlay.visible=true`(绑四处可见性/open-close 调用记录;`_screen_seals()` :51-57 互斥复跑) | 新增:Esc 浮窗唤起与三出口动线 |
| V4 设置项(双路径) | `main_esc_flow.gd _on_settings()`;`main_overlay_flow.gd _on_settings_closed` 语境分支 | `screen_flow_probe.gd:_ready`(营地浮窗→设置→关闭回浮窗段)〔ESC批 V4〕+菜单路径冒烟 V3g settings 段复跑 | 探针(+冒烟复跑) | 浮窗「设置」钮 → `ui.modals.flow.open(settings_overlay)` → 关闭 → `_on_settings_closed` 语境分支 | 设置开→浮窗 `visible` 恒 true(open 只隐登记序上方层——modal_stack.gd:27-31,浮窗登记在下、由树序遮挡非属性隐藏);关闭→浮窗重现+`start_overlay.visible=false`(绑语境分支谓词=camp_esc_overlay.visible;反向断言「关闭设置后禁弹主菜单」) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| V5 退出项 | `main_esc_flow.gd _on_quit()` → `main.gd:143 _quit_game()`(原样复用) | `ui_struct_probe.gd:_ready`(退出钮接线结构断言)〔ESC批 V5〕 | 探针(源码/结构,headless 不可真退) | 浮窗「退出游戏」钮 `pressed` → `_on_quit()` → `_quit_game()`(`settings_store.save_settings()`+`get_tree().quit()`) | 钮 `pressed` 连接目标=`_on_quit`→`_quit_game`(绑信号连接与调用链;headless 不真退=静态判据+评审注记申报观测面局限);**禁对退出钮 `pressed.emit()`**(防 `get_tree().quit()` 中止探针进程——quit(0) 仅靠 probe_gate 签名兜底不假绿,2026-11-01 双审补立) | 新增:Esc 浮窗唤起与三出口动线 |
| V6 HUD 余额 | `camp_hud_band.gd set_balance()`/`bind_refresh_bus()`/`refresh()`;`camp_scene_view.gd set_hud_band()` 填值;挂接卡④ `CampRefreshBus.refresh()` | `screen_flow_probe.gd:_ready`(HUD 余额读数/刷新段)〔ESC批 V6〕+`ui_struct_probe.gd:_ready`(顶栏位结构段) | 探针(跨卡联验:购买后刷新挂卡④单口) | 隔离存档实例 `meta_state._add_keystone(9999)`(`ui_click_probe.gd:74` 先例)→ 显式触发 `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` 读数 | `value_label.text`=`"钥石 %d" % camp_data()["balance"]` 格式串比较(badge 文本带「钥石 」前缀——`ui_kit.gd:632` 工厂约定单点,禁实现期自造第二格式串;`keystone_balance()` 单口取值,2026-11-01 双审订正);直写后显式触发刷新才变(绑单口调用记录+badge 文本状态源) | 既有:元进度入账与锻造两轴;新增:营地顶栏 HUD 钥石读数与刷新单口同步 |
| V7 零动效 | `camp_esc_overlay.gd` 直开直关(零 AnimationPlayer、源码禁 `create_tween`/`UiKit.motion`) | `ui_struct_probe.gd:_ready`(浮窗零动效契约段,仿 career :379-391)〔ESC批 V7〕 | 探针(结构/源码扫描) | 浮窗视图子树+`camp_esc_overlay.gd` 源码 | 两新件源码(camp_esc_overlay.gd+main_esc_flow.gd)+浮窗子树零 AnimationPlayer+源码零 `create_tween`/`UiKit.motion` 令牌(扫描面 >0 非空前置;扫描面扩两新件——2026-11-01 双审补立,开合编排件不脱扫) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| V8 层序与遮挡 | `ui_registry.gd` `LAYER_CONTRACT` 加 `camp_esc_overlay` 槽(营地层与 `settings_overlay` 之间)+`node()` 寻址 | `ui_struct_probe.gd:_ready`(浮窗登记序/遮挡段)〔ESC批 V8〕+`system_test.gd:_ready`(V1c 合同树序链 :94-106 复跑) | 探针(+系统测试复跑) | `main.ui.LAYER_CONTRACT` 单一事实源;`ui.modals.flow.open()` 遮挡记账 | 登记册序=合同模态序、直接子层树序=合同序逐对相等;`debug_badge` 末位 float 类不注册(open 只动登记层,绑登记册成员性;F10 发布态恒隐,不绑 `visible==true`) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| V9 目检项登记 | `reports/iter<N>-实现报告-<日期>.md` 实机目检总清单 | 件核对(`体验验证清单.md` 登记,登记不阻塞)〔ESC批 V9〕 | 用户目检(非自动化) | 用户实机(生命周期末步统一进行,不阻塞迭代) | Esc 三动线+HUD 余额观感+浮窗视觉面(文字 WCAG 若入 shot_checker 取样,否则显式登记归目检并写明理由)入 `体验验证清单.md`=登记即义务了结 | 既有:模态覆盖层编排与输入锁 |
| V10 反哺点清单 | §5 每条有落点 | 件核对表〔ESC批 V10〕 | 件核对(非自动化) | §5 同步面逐条对账 | §5 每条有 `reports/iter<N>-反哺点-<日期>.md` 落点 | 既有:模态覆盖层编排与输入锁 |
| B1 Esc 输入接线与浮窗视图(行为改动专行) | `main.gd` 输入入口/薄委托;`main_esc_flow.gd on_esc_input()`/`is_camp_context()`/`open_overlay()`/`close_overlay()`;`camp_esc_overlay.gd` 新建;`ui_registry.gd` 新槽 | `screen_flow_probe.gd:_ready`(Esc 唤起/关窗/非营地段)+`ui_struct_probe.gd:_ready`(三钮/零动效/登记序段)〔ESC批 B1〕 | 集成 | 营地场景根态合成 Esc 键 → `main_esc_flow` → `ui.modals.flow.open/close(camp_esc_overlay)` | Esc 输入→浮窗 visible 两态(绑 open/close 调用记录);语境判据返回值(绑 `is_camp_context()`);浮窗三钮数量+登记序(绑节点树与合同表) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| B2 HUD 栏落位与刷新挂单口(行为改动专行) | `camp_hud_band.gd` 新建;`camp_scene_view.gd set_hud_band()` 填值;订阅卡④ `CampRefreshBus.refresh()` | `screen_flow_probe.gd:_ready`(HUD 读数/刷新段)+`ui_struct_probe.gd:_ready`(顶栏位结构段)〔ESC批 B2〕 | 集成 | `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` | badge 文本=`camp_data()["balance"]` 单口取值;顶栏位存在(结构断言,高 48px 浮层);刷新单口调用记录(跨卡联验) | 既有:元进度入账与锻造两轴;新增:营地顶栏 HUD 钥石读数与刷新单口同步 |
| B1 Esc 输入接线与浮窗视图(行为改动专行) | `main.gd` 输入入口/薄委托;`main_esc_flow.gd on_esc_input()`/`is_camp_context()`/`open_overlay()`/`close_overlay()`;`camp_esc_overlay.gd` 新建;`ui_registry.gd` 新槽 | `screen_flow_probe.gd:_ready`(Esc 唤起/关窗/非营地段)+`ui_struct_probe.gd:_ready`(三钮/零动效/登记序段)〔ESC批 B1〕 | 集成 | 营地场景根态合成 Esc 键 → `main_esc_flow` → `ui.modals.flow.open/close(camp_esc_overlay)` | Esc 输入→浮窗 visible 两态(绑 open/close 调用记录);契约 9 四态路由(绑现开集合判据与 open/close 调用记录,含「设置开在浮窗之上」无反馈态);浮窗三钮数量+登记序(绑节点树与合同表) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
| B2 HUD 栏落位与刷新挂单口(行为改动专行) | `camp_hud_band.gd` 新建;`camp_scene_view.gd set_hud_band()` 填值;挂接卡④ `CampRefreshBus.refresh()` | `screen_flow_probe.gd:_ready`(HUD 读数/刷新段)+`ui_struct_probe.gd:_ready`(顶栏位结构段)〔ESC批 B2〕 | 集成 | `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` | badge 文本=`camp_data()["balance"]` 单口取值;顶栏位存在(结构断言,高 48px 浮层);刷新单口调用记录(跨卡联验) | 既有:元进度入账与锻造两轴;新增:营地顶栏 HUD 钥石读数与刷新单口同步 |
| B3 设置语境分支与返回主动线接线(行为改动专行) | `main_esc_flow.gd _on_return_to_menu()`/`_on_settings()`/`_on_quit()`;`main_overlay_flow.gd _on_settings_closed` 语境分支 | `screen_flow_probe.gd:_ready`(返回动线/设置双路径段)+`ui_struct_probe.gd:_ready`(退出钮接线断言)〔ESC批 B3〕 | 集成 | 浮窗三钮 `pressed` → `main_esc_flow` 三出口 → 现役动线/`_quit_game`;设置关闭 → `_on_settings_closed` 语境分支 | 返回动线三处可见性(绑屏显编排);设置关闭后浮窗 `visible=true`+`start_overlay.visible=false`(绑语境分支);退出钮连接链(绑信号连接) | 既有:模态覆盖层编排与输入锁;新增:Esc 浮窗唤起与三出口动线 |
**落点集中**:V1/B1 → T1/T3;V2/V7/V8 → T1/T3;V3/V4/V5/B3 → T3;V6/B2 → T2/T3;V9/V10 → T4。全表名实核对归 T4。
@@ -248,7 +258,7 @@
## 8. 异常分级
- **L1(自救一次)**:断言写法、探针合成键事件写法、命名、编译错误、签名拼写、测试套件 manifest 登记遗漏。
- **L2(记录继续;同一红连续三次升 L3)**:Esc 接线方式选型(§9 第 1 条域内)、浮窗壳尺寸/位次(UiKit 现行件域内)、HUD 栏位微调(顶栏域内)、行数较 T0 预期漂移但 S13 键不升。
- **L2(记录继续;同一红连续三次升 L3)**:浮窗壳尺寸/位次(UiKit 现行件域内)、HUD 栏位微调(顶栏域内)、行数较 T0 预期漂移但 S13 键不升。
- **L3(立即停卡写报告等用户)**:
- 卡④ 代码波未到位又需合并;
- Q1/Q2/Q3 裁定被执行期擅自改值(已裁=甲/甲/甲,见 §10);
@@ -260,8 +270,8 @@
## 9. 代拍板白名单(超清单必呈报)
1. Esc 输入接线方式(`_unhandled_input` vs InputMap 新 action vs 专用控制器)——约束:不吞他处输入、探针可合成;现 `screen_flow_probe.gd:156-160` 已有 `Viewport.push_input(InputEventKey)` 合成先例,T0 复核该先例与所选路线的兼容性(InputMap action+轮询路线与合成探针的兼容性不预断,T0 定稿)。
2. 浮窗模态壳尺寸与位次(UiKit `modal_shell` 现行件域内,`ui_kit.gd:487`)。
1. **(v1.4 已定稿,移出白名单)Esc 接线=事件驱动 `_unhandled_input`+`KEY_ESCAPE`(physical_keycode)、禁 Input 单例轮询、不挂 ui_cancel、pressed 且非 echo**——契约 9 接线定稿(2026-11-01 用户裁);白名单保留此条作裁定留痕,实现期无选型自由。
2. 浮窗模态壳尺寸与位次(UiKit `modal_shell` 现行件域内,`ui_kit.gd:486`)。
3. HUD 栏在顶栏的左右位次与间距(卡④场景屏顶栏域内)。
4. 「返回主菜单」复用卡④篝火同一处理函数(建议复用——动线同口;实现期定)。
5. 浮窗视觉面是否入 shot_checker 取样(T3 定:入=补截图段;不入=显式登记归目检并写明理由)。
@@ -284,11 +294,11 @@
连带:判据形态随案(T1 已列);V1 按案分拆断言;契约 2 措辞裁后回写。
**裁定(2026-10-18)**:**甲**——只营地根态响应 Esc;子界面开着时 Esc 不唤浮窗。代拍板留痕(用户令「事项按推荐的来」,本项无推荐案,取默认甲=语义最简;前提=卡④ T3 承诺各子界面自带返回钮闭环,卡④ v3.0 已在内;追认单可 veto)。T1 判据=营地场景屏 visible 且无营地子模态开态。裁定落 T0 执行+实现报告留痕。
**裁定(2026-10-18)**:**甲**——只营地根态响应 Esc;子界面开着时 Esc 不唤浮窗。代拍板留痕(用户令「事项按推荐的来」,本项无推荐案,取默认甲=语义最简;前提=卡④ T3 承诺各子界面自带返回钮闭环,卡④ v3.1 已在内;追认单可 veto)。T1 判据=营地场景屏 visible;「无营地子模态开态」由契约 9① 现开集合判据承载(v1.4,判据单点化)。裁定落 T0 执行+实现报告留痕。
### Q2 · 浮窗的关闭/取消路径(已裁定 甲)
**背景**:`UiKit.modal_shell`(`ui_kit.gd:487`)只产 dim+center+panel+VBox、**无内置关闭件**;契约 1 禁加项⇒浮窗只有三钮,无一「关闭」。而 §4 不做清单把「任意键关闭浮窗」划为用户未裁禁自扩——Esc 自身再按的行为恰好落在被 punt 的缝隙里:Q1 两案都不覆盖「浮窗已开时按 Esc」。若不裁且无关闭钮,玩家误开浮窗后离开营地只有「回主菜单/开设置/退游戏」三条路——体感陷阱,且与卡④子界面(生涯有返回钮)的可用性水准倒挂。
**背景**:`UiKit.modal_shell`(`ui_kit.gd:486`)只产 dim+center+panel+VBox、**无内置关闭件**;契约 1 禁加项⇒浮窗只有三钮,无一「关闭」。而 §4 不做清单把「任意键关闭浮窗」划为用户未裁禁自扩——Esc 自身再按的行为恰好落在被 punt 的缝隙里:Q1 两案都不覆盖「浮窗已开时按 Esc」。若不裁且无关闭钮,玩家误开浮窗后离开营地只有「回主菜单/开设置/退游戏」三条路——体感陷阱,且与卡④子界面(生涯有返回钮)的可用性水准倒挂。
| 案 | 内容 | 代价 |
|---|---|---|
@@ -1,16 +1,16 @@
# 需求卡片:营地场景图新建批(交互式营地 · 七物件绑定 · 四柜台拆解)· 营地交互重做卡④
- 版本:**v3.0(现行有效卡)**。留痕:2026-10-21 用户令退回美术设计段待闭合;2026-10-31 用户令重立——美术设计已闭合(样张过审+art-bible 条款版本),据 req-card 第〇步重立为现行有效卡,零设计待裁点。
- 来源:[需求澄清-营地交互重做-2026-10-18.md](需求澄清-营地交互重做-2026-10-18.md)(目标体验原话/目标结构树+纵深稿/裁决 5·6·7·8·10/验收方式/切批原则④)+ 样张过审(2026-10-31 用户裁定,清单 [artwork/candidates/_终稿裁定-2026-10-31.txt](../artwork/candidates/_终稿裁定-2026-10-31.txt):各素材第一张样本稿为终稿、保留全部待选稿)+ [art-bible.md](../design/art/art-bible.md) v3.24 §6.2(营地场景 10 层口径)(**唯一事实源**:样张过审 2026-10-31 + art-bible.md v3.24 §6.2;配套 [camp.md](../design/gdd/ux/camp.md) v2.2/[camp-scene-prompts.md](../design/art/camp-scene-prompts.md) v1.2/[asset-path-map.md](../design/art/asset-path-map.md)/[art-assets-checklist.md](../design/art/art-assets-checklist.md) §3.5 同步;需求澄清为目标体验事实源)
- 版本:**v3.2(现行有效卡)**。留痕:2026-10-21 用户令退回美术设计段待闭合;2026-10-31 用户令重立——美术设计已闭合(样张过审+art-bible 条款版本),据 req-card 第〇步重立为现行有效卡,零设计待裁点;2026-11-01 grilling+godot-master 双审后用户令修订(v3.1)——§0.3 四道代码段开工门裁断销号、§2 换新盘面、Godot 风险项落卡;2026-11-01 用户裁机检预算互斥停机呈报(v3.2)——营地六句柄改落 `ui_camp_modals.gd` 子容器(卡⑥ 前置建件),`ui_modals.gd` 类变量 14→14 零基线,存量触面一查补列 ui_modals/ui_registry/main_ui_build 三件,全量分域分家登记待办清单另立批。
- 来源:[需求澄清-营地交互重做-2026-10-18.md](需求澄清-营地交互重做-2026-10-18.md)(目标体验原话/目标结构树+纵深稿/裁决 5·6·7·8·10/验收方式/切批原则④)+ 样张过审(2026-10-31 用户裁定,清单 [artwork/candidates/_终稿裁定-2026-10-31.txt](../artwork/candidates/_终稿裁定-2026-10-31.txt):各素材第一张样本稿为终稿、保留全部待选稿)+ [art-bible.md](../design/art/art-bible.md) v3.31 §6.2(营地场景 10 层口径)(**唯一事实源**:样张过审 2026-10-31 + art-bible.md v3.31 §6.2;配套 [camp.md](../design/gdd/ux/camp.md) v2.2/[camp-scene-prompts.md](../design/art/camp-scene-prompts.md) v1.2/[asset-path-map.md](../design/art/asset-path-map.md)/[art-assets-checklist.md](../design/art/art-assets-checklist.md) §3.5 同步;需求澄清为目标体验事实源)
- 归属:呈现层+美术线(`game/presentation/` + 场景素材)——营地交互重做五卡之四,**最大件**
- 排期:未编入(2026-10-18 用户令:需求卡片不预占迭代号,批次归属按工作量另裁)
- 开工门:**零设计待裁点,开工门=U0**;编入迭代由 iter-plan 段发令,代码段按 `/iter` 开工,代码段开工门见 §0(不阻塞本卡作为有效卡与编迭代)
## 🔧 状态:现行有效卡(v3.0,2026-10-31 重立)
## 🔧 状态:现行有效卡(v3.1,2026-11-01 双审修订)
本卡设计线已闭合,凭据见 §0「重立凭据」:样张过审(2026-10-31)+ art-bible v3.24 §6.2 条款版本齐备。设计待裁点清零、开工门=U0;未编入迭代,编入由 iter-plan 段发令。属呈现层+美术线,五卡之四。
本卡设计线已闭合,凭据见 §0「重立凭据」:样张过审(2026-10-31)+ art-bible v3.31 §6.2 条款版本齐备。2026-11-01 grilling+godot-master 双审通过、§0.3 四道代码段开工门全部裁断销号(用户照双审推荐采纳),设计待裁点与开工门清零;已编入迭代 47(roadmap.md 迭代总表,与卡⑤ 同波,卡⑤ 随本卡代码波之后)。属呈现层+美术线,五卡之四。
**归属纪律**:本卡只登记代码段开工门与已裁断。前置由谁解决、何时解决、设计稿与素材由谁产出由用户定——美术/设计线=用户自立批(铁律 2),Agent 不代跑、不代产、不自行开工设计稿。素材终稿落 `game/assets/art/` 属实现段 T1/`asset-ingest` 第 3 步,不属写卡门(用户 2026-10-31 令:只生成素材、不做后处理,开工时另发令)。
**归属纪律**:本卡只登记代码段开工门与已裁断。前置由谁解决、何时解决、设计稿与素材由谁产出由用户定——美术/设计线=用户自立批(铁律 2),Agent 不代跑、不代产、不自行开工设计稿。素材终稿落 `game/assets/art/` 属实现段 T1/`asset-ingest` 第 3 步,不属写卡门(用户 2026-10-31 令:只生成素材、不做后处理;2026-11-01 用户裁:后处理授权随 /iter 开工令一并生效,见 §0.3-4)。
**批内批次次序(铁律 2:美术素材走自立批、不占迭代位;先产图后改代码)**:美术设计已闭合(样张过审)→ 场景素材后处理与入库(自立批,产物 `artwork/`→`game/assets/art/scene/`)→ 本卡代码段 `/iter`(与卡⑤ 同波)。工程面 ①②③ 代码已随迭代 33 合回(卡①②③ 已按吸收即死退役);④⑤ 未编排,编入哪一迭代由用户经迭代编排段发令。
@@ -21,7 +21,7 @@
req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(样张过审+art-bible 条款版本)。两样已齐:
- **样张过审**=用户 2026-10-31 裁定「保留全部待选稿、各素材第一张样本稿为终稿、每个都还可以接受」(清单 `artwork/candidates/_终稿裁定-2026-10-31.txt`),用户准备在游戏内做最终审核。
- **art-bible 条款版本**=`art-bible.md v3.24 §6.2`(营地场景 10 层口径;camp.md v2.2/camp-scene-prompts.md v1.2/asset-path-map.md/art-assets-checklist.md §3.5/评估报告同步)。
- **art-bible 条款版本**=`art-bible.md v3.31 §6.2`(营地场景 10 层口径;camp.md v2.2/camp-scene-prompts.md v1.2/asset-path-map.md/art-assets-checklist.md §3.5/评估报告同步)。
两样齐 ⇒ 重立卡判据齐,本卡为现行有效卡。P3/P5 等未销号项属**代码段开工门**,不阻塞重立卡。
@@ -31,18 +31,18 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| -- | ------- | ---- | ---- | ---- |
| P1 | Q1 美术渠道裁定(A/B) | 用户 | **已裁=B·插画级新产**(2026-10-22);✦ 渠道作者自跑远程生图管线,规格沿用 art-bible §6.2/§8.1/§8.2 既有裁决 | art-assets-checklist.md §3.5 |
| P2 | 像素规格 | 用户线 | **Q6 甲过渡口径已裁**:现役 1280 工程档(1280×800)+烛光黄 #F6C667/木色 #8A5A34;2× 源档出图、最近邻降采样;终档归样张批冻结清单后置换(反哺点登记),执行归用户线 | 契约 8 |
| P3 | 点选反馈设计稿(悬停高亮/提示文案/可点区域判定) | 用户线 | **代码段开工门**:camp.md v2.2 已定四态规格;设计稿细化未受命开工,Agent 不代跑不代产 | §0.3 |
| P3 | 点选反馈设计稿(悬停高亮/提示文案/可点区域判定) | 用户线 | **已销号(2026-11-01 用户裁)**:销号口径=camp.md v2.2:35-37 四态规格即点选反馈设计稿(四态参数已钉死);剩余实质=七物件名牌文案与可点区域视觉,归 T2 域内(名牌文案按 §1 绑定表界面名逐字落、可点区域判定归 §9 白名单 3) | §0.3 |
| P4 | 参照物 | 用户 | **已补**(2026-10-23)=`artwork/2026-10-23-营地场景/Gemini_Generated_Image_ykrc0nykrc0nykrc.jpg`,已进 camp-scene-prompts.md §1 登记为「作者提供的参照物」,与纵深稿对过表、围墙结构冲突已呈报;参照物只是参考图,保留 `artwork/`、不入 git、不入游戏目录 | camp-scene-prompts.md §1 |
| P5 | GDD/art-bible 营地条款落文 | 文档批(用户发令) | **代码段开工门**:camp.md v2.2/art-bible v3.24 §6.2 已先行落文新形态;实装状态回填与 §7.5/§2.8 同步归文档批,实现批禁写 `design/` | §5 反哺点 |
| P5 | GDD/art-bible 营地条款落文 | 文档批(用户发令) | **已销号(2026-11-01 用户裁)**:销号口径=形态落文完成(camp.md v2.2/art-bible v3.31 §6.2 已在盘);实装状态回填与 §7.5/§2.8 同步归 T6 反哺点义务(§5),非停机条件,实现批禁写 `design/` | §5 反哺点 |
### 0.3 代码段 `/iter` 开工门(不是设计待裁点,不阻塞重立卡与编迭代)
下列项属代码段开工门;未销号即需开工对应任务段=§8 L3 停机呈报,禁批内自补、禁 T0 现场发挥:
1. **P3 点选反馈设计稿**:悬停高亮/提示文案/可点区域判定三项细化稿(camp.md v2.2 已定四态规格与名牌参数,设计稿只细化呈现,不阻塞本卡成立)。
2. **P5 GDD/art-bible 条款落文**:文档批对实装状态、§7.5/§2.8 的回填(实现批禁写 `design/`,逐条列 `reports/iter<N>-反哺点-<日期>.md`)。
3. **camp-scene-prompts.md §5 五个待裁点**:帐篷尺寸(现 300×170 待重定)/门扇高宽(现 240×220,围墙带仅 43px 高、叠放关系待裁)/布告板位置(现 y330 悬空、建议 (845,268,130,140) 挂回围墙带)/描边颜色追认(现按奶油色、去底技术前提,改深灰褐须换去底方案)/营地环境子板色值(art-bible §4.1 数值单点,待作者拍板)。五点阻塞坐标表定稿与接线,不阻塞出图(样张已过审)。
4. **十张终稿后处理与入库**:终稿此刻=原始候选图本身(带通道水印、浅灰不透明底、画布 2560×1600 或 1024×1024),未做去底/擦水印/降采样/裁围墙细条;落 `game/assets/art/scene/` 属实现段 T1/`asset-ingest` 第 3 步,用户 2026-10-31 令只生成素材不做后续工序,开工时另发令。
1. **P3 点选反馈设计稿——已销号(2026-11-01 用户裁)**:销号口径=camp.md v2.2:35-37 四态规格即点选反馈设计稿(悬停/按下/禁用/常态参数已钉死);剩余实质=七物件名牌文案与可点区域视觉,归 T2 域内(名牌文案按 §1 绑定表界面名逐字落,可点区域判定归 §9 白名单 3)。
2. **P5 GDD/art-bible 条款落文——已销号(2026-11-01 用户裁)**:销号口径=形态落文完成(camp.md v2.2/art-bible v3.31 §6.2 已在盘);实装状态回填与 §7.5/§2.8 同步归 T6 反哺点义务(§5),非停机条件。
3. **camp-scene-prompts.md §5 五裁点——已裁断(2026-11-01 用户裁,照双审推荐采纳)**:①帐篷 300×170 追认现值 ②门扇 240×220 追认现值、坐标按门洞 x520–760 对齐锚点 ③布告板 (845,268,130,140) 挂围墙带(y330 悬空案弃)④描边颜色=奶油色追认(去底技术前提随现值锁定,改深灰褐须换去底方案全批重出、禁)⑤营地环境子板色值**不前置**——T1 量化实际用色后按 art-bible §4.1 两道门(语义检查+RGB 三通道差)登记;未登记前 T5「截屏级 48 色计数是否新建」(§9-4)为条件步:色值已登记→拍板,未登记→该子步显式跳过+T6 登记遗留(不阻塞批末闸,素材级二器 palette_check/standee_asset_check 已覆盖素材级执法)。camp-scene-prompts.md §5 节改已裁+art-bible :398 对应三处值落文归文档批(§5 反哺点表已列)。
4. **十张终稿后处理与入库——发令形态已裁(2026-11-01 用户裁)**:**/iter 开工令即含 T1 后处理授权**(去底转透明〔bg 不去底、全幅不透明〕/擦通道水印/降采样/裁围墙细条/色板量化),不再两次发令;逐张画布档→目标矩形换算式登记见 §6 T1(1024×1024 档无整数倍降采样,禁现场默认)。
### 0.4 10 层拆分(现行口径,覆盖旧 8/9 张)
@@ -51,7 +51,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| z0 | `scene_camp_bg` | 不透明全幅=天空+远山+整片地面+门洞内一条通向远处的小路 | 否 |
| z1 | `scene_camp_wall` | 透明底,横贯全宽密排竖木桩墙(左右两段)+两根略高门柱;门洞 x520–760 留透明缺口透出 bg 小路;落盘按细条 (0,240,1280,70) 裁、不做全幅 | 否 |
| z2 | `scene_camp_board` 布告板 | 挂围墙带,点进图鉴占位 | 是 |
| z3 | `scene_camp_gate` 门扇 | 半开双扇,叠在门洞上(高宽待 §0.3 裁) | 是 |
| z3 | `scene_camp_gate` 门扇 | 半开双扇,叠在门洞上(240×220 追认现值、2026-11-01 裁;坐标按门洞 x520–760 对齐) | 是 |
| z4 | `scene_camp_dummy` 木桩/`scene_camp_forge` 锻炉/`scene_camp_tent` 帐篷 | 木桩、锻炉可点;帐篷承载件不可点 | 部分 |
| z5 | `scene_camp_chest` 宝箱/`scene_camp_notebook` 笔记本 | 宝箱、帐篷旁笔记本 | 是 |
| z6 | `scene_camp_campfire` 篝火 | 最前;命中矩形只取下半(火堆),上半火焰只展示 | 是 |
@@ -105,29 +105,29 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
6. 美术约束(48 色板/禁入带/语义色/WCAG)入闸或登记缺口;**用户目检项登记 07**(生命周期末步,不阻塞);
7. 批末重闸按改动域选档全绿;三份 `reports/` 产出物写齐(T6)。
## 2. 现状事实(开工日 T0 复核行号)
## 2. 现状事实(2026-11-01 双审全量复核换新盘面;开工日 T0 仍按「漂移即停卡核对」抽核)
| # | 事实 | 出处 |
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| F1 | 营地场景图完全不存在(澄清④判定「完全没有 · 新建」);现营地=CampOverlay 四柜台覆盖层(树淬炼/锻造工坊/秘宝匣/奖励格图鉴并排,迭代 21 T6 程序占位重排——代码注释原文「视觉终稿归 HUD 营地视觉批」) | `需求澄清-营地交互重做-2026-10-18.md` §④;`game/presentation/views/camp_overlay_view.gd:86-87` |
| F2 | 营地屏壳(CampScreen)持 CampOverlay 成员;组合根 `main.camp_overlay` 句柄指向屏壳成员(探针挂载假设) | `game/presentation/scenes/camp_screen.gd:8-14`;`main.gd:112` |
| F3 | CampOverlay 结构:顶栏铭牌+钥石徽(:64-84);四柜台并排横滚(树淬炼 :99-109/锻造工坊 :111-137/秘宝匣 :139-159/奖励格图鉴 :161-172);底栏关闭钮+最近冒险条带(:174-189);钥匙门判据读树投影(:287-296);图鉴柜台=结构位预留(:168「内容排 Alpha」) | `camp_overlay_view.gd` 各处 |
| F4 | 五目标界面现状:锻造工坊=CampOverlay 内工作间(`_forge_workbench` 两区六路线);秘宝匣=`_ws_scroll`/`_workshop_holder`(12 件+同调装配段+Y 升级行);天赋树=`_tree_holder`(脉分组行板+Fork 双态钮);图鉴=占位;生涯=**已独立全屏页**(CareerOverlay,入口现居主菜单生涯钮,自带返回钮) | `camp_overlay_view.gd:116-137`/:144-159/:99-109/:161-172;`views/career_overlay.gd`;`main.gd:332` |
| F1 | 营地场景图完全不存在(澄清④判定「完全没有 · 新建」);现营地=CampOverlay 四柜台覆盖层(树淬炼/锻造工坊/秘宝匣/奖励格图鉴并排,迭代 21 T6 程序占位重排)——原引注释「视觉终稿归 HUD 营地视觉批」已随后续重构删除(2026-11-01 复核,引文退役、事实不变) | `需求澄清-营地交互重做-2026-10-18.md` §④;`game/presentation/views/camp_overlay_view.gd` |
| F2 | 营地屏壳(CampScreen)持 CampOverlay 成员;组合根句柄=`main.ui.modals.camp_overlay`(ui_modals.gd:14;ui_registry.gd:25 LAYER_CONTRACT 模态槽)——`main.camp_overlay` 旧寻址已退役,探针挂载随句柄迁移(F8) | `game/presentation/scenes/camp_screen.gd:8-14`;`game/presentation/ui_modals.gd:14`;`game/presentation/ui_registry.gd:25` |
| F3 | CampOverlay 结构(结构在、行号以 2026-11-01 复核为基准):顶栏铭牌+钥石徽(:125);四柜台并排横滚(树淬炼/锻造工坊/秘宝匣/图鉴——图鉴锁文现 :207);底栏关闭钮+最近冒险条带(:214 起,`_rebuild_recent` :634);钥匙门判据读树投影 | `camp_overlay_view.gd` 各处 |
| F4 | 五目标界面现状:锻造工坊=CampOverlay 内工作间(`_forge_workbench` 两区六路线);秘宝匣=`_ws_scroll`/`_workshop_holder`(12 件+同调装配段+Y 升级行);天赋树=`_tree_holder`(脉分组行板+Fork 双态钮);图鉴=占位;生涯=**已独立全屏页**(CareerOverlay,入口现经组合根函数直调——卡②已撤主菜单生涯钮、改函数直调中间态,shot_probe.gd:103/ui_struct_probe.gd:59「careerbtn 已撤」注;终态=本批改挂帐篷旁笔记本,自带返回钮) | `camp_overlay_view.gd` 四柜台各处;`views/career_overlay.gd` |
| F5 | 营地购买写口链路:视图只发意图信号(`buy_requested`/`tree_buy_requested`/`forge_*` 等),组合根经 CampShopPresenter 转 MetaProgression 写口;投影数据=`camp_shop_presenter.gd:62-72` `camp_data()` 单点 | `camp_overlay_view.gd:8-15`;`camp_shop_presenter.gd:62-72` |
| F6 | 断点确认弹窗现居主菜单开始冒险路径(有断点先出 ConfirmDialog 再开局)——随卡②撤钮整体移交本卡大门 | `main.gd:475-497` |
| F7 | 冒险屏已实现不动:选路屏(LevelMapScreen,`route_chosen` 前进唯一通道)与战斗屏宿住 AdventureScreen;组合根侧:`_start_adventure_from_menu`(:475-484)/`_on_confirm_new_adventure`(:487-492)/`_resume_from_menu`(:518-536)/`_start_adventure`(:579-597)/`build_adventure_params`(:564-574) | `views/level_map_screen.gd:7`;`scenes/adventure_screen.gd:36-37`;`main.gd` 各处 |
| F8 | 探针面营地段(全部随本批迁移):ui_probe(营地入口+余额+秘宝匣 12 行断言 :28-55,深员 `_balance_label`/`_tree_buy_btns`/`_workshop_rows` :39-52)、ui_click_probe(营地购买全链 :53-127;**断点确认段 :500-528**〔经 lvbtn 驱动 ConfirmDialog 取消/开新冒险——被卡②撤钮+本批弹窗移位双杀〕)、screen_flow_probe(营地可见态键 :53 直读 `main.camp_overlay.visible`;营地开合 :109-125 回退段直呼 `main.camp_overlay.closed.emit()`;菜单→冒险 lvbtn 步 :130-136)、shot_probe(camp 截图+「关闭营地」钮收闭 :126-133)、ui_struct_probe(camp 结构审计 :41-49;同槽特例断言 :331/:373)、smoke_all(V2 营地屏壳实例化 :170-171;V3g 营地模态开合 :405-435;**camp_overlay_view.gd 源码断言段 :6469-6475**〔三条锁四柜台等高形态本身——`Vector2(0,336)`×3/`SIZE_FILL`×4/`SIZE_SHRINK_CENTER`≥3,随形态退役〕)、ui_contrast_probe(营地令牌段 :48-55〔UiKit 令牌恒等式,不随拆解红;新界面若不再消费 `CAMP_HEADER_BAND`/`CAMP_ROW_PLATE` 则段标签虚挂,须核签〕)、**forge_workbench_test.gd 单测**(:33 直载 camp_overlay_view.gd 并消费 `_forge_rows`/`_forge_workbench`/`open_camp`/`refresh_camp`/三 forge 信号——CampOverlay 一删即红) | `game/smoke/` 各处+`game/tests/unit/presentation/forge_workbench_test.gd:33` |
| F6 | 断点确认链已随卡②摘除(main.gd:118-120 注释「弹窗本体与两分支逻辑归卡④大门承接」);main.gd:475-497 现为选档委托(旧「断点弹窗现居主菜单」引文失实,2026-11-01 复核)——断点确认弹窗本卡 T4 承接(卡②移交) | `main.gd:118-120` |
| F7 | 冒险屏已实现不动:选路屏(LevelMapScreen,`route_chosen` 前进唯一通道)与战斗屏宿住 AdventureScreen;组合根侧(2026-11-01 复核行号):`_start_adventure_from_menu` :116/`_resume_from_menu` :150/`build_adventure_params` :183/`_start_adventure` :198——`_on_confirm_new_adventure` 已随卡②摘除(T4 重立消费方编排) | `views/level_map_screen.gd:7`;`scenes/adventure_screen.gd:36-37`;`main.gd` 各处 |
| F8 | 探针面营地段(全部随本批迁移;宿主=`game/probe/`+`game/tests/`——`game/smoke/` 目录已于 2026-10-24 退役(git 16c6f136)、smoke_all.gd 已整体退役(git 957391e1),v3.0 的 `game/smoke/*`+smoke_all 旧盘面写法作废):ui_probe(营地入口+余额+秘宝匣 12 行断言,深员 `_balance_label`/`_tree_buy_btns`/`_workshop_rows`)、ui_click_probe(营地购买全链;**断点确认段**〔经 lvbtn 驱动 ConfirmDialog——被卡②撤钮+本批弹窗移位双杀〕)、screen_flow_probe(营地旗标 :54 直读主菜单句柄——:53 是菜单行,v3.1 订正;营地开合 :119-125 回退段;菜单→冒险 lvbtn 步)、shot_probe(camp 截图+「关闭营地」钮收闭;生涯段函数直调驱动 :103)、ui_struct_probe(camp 结构审计;同槽特例断言)、system_test.gd(V2 营地屏壳实例化 :67-68;V3g 营地模态开合 :520-541)、theme_projection_test.gd:710-717(四柜台等高源码断言段 `_smoke_element_details`——`Vector2(0,336)`×3/`SIZE_FILL`×4/`SIZE_SHRINK_CENTER`≥3,随形态退役)、ui_contrast_probe(营地令牌段〔UiKit 令牌恒等式,不随拆解红;新界面若不再消费 `CAMP_HEADER_BAND`/`CAMP_ROW_PLATE` 则段标签虚挂,须核签〕)、**forge_workbench_test.gd 单测**(:33 直载 camp_overlay_view.gd 并消费 `_forge_rows`/`_forge_workbench`/`open_camp`/`refresh_camp`/三 forge 信号——CampOverlay 一删即红) | `game/probe/` 各处+`game/tests/system_test.gd:67-68`/:520-541+`game/tests/unit/presentation/theme_projection_test.gd:710-717`+`game/tests/unit/presentation/forge_workbench_test.gd:33` |
| F9 | camp UX spec 现行 **camp.md v2.2** 已落文可点击场景图形态(定位/场景构成与七物件绑定/10 层拆分/点选反馈四态/顶栏 48px/叠层与开合);本批实现=**对齐** v2.2,旧三柜台形态已由 v2.2 取代,本批只需回填实装状态与目检结论 | `design/gdd/ux/camp.md:10-51` |
| F10 | art-bible 现行 **v3.24 §6.2** 已落文营地场景 10 层口径(z0 bg 不透明全幅/z1 wall 透明细条门洞缺口/八物件层/篝火命中只下半/围墙落盘 (0,240,1280,70));§7.5 营地界面已改为「可点击场景图+七物件点选」画法;§2.8 承载元素同述;本批实现=对齐现行条款 | `design/art/art-bible.md` §6.2/§7.5/§2.8 |
| F10 | art-bible 现行 **v3.31 §6.2** 已落文营地场景 10 层口径(z0 bg 不透明全幅/z1 wall 透明细条门洞缺口/八物件层/篝火命中只下半/围墙落盘 (0,240,1280,70));§7.5 营地界面已改为「可点击场景图+七物件点选」画法;§2.8 承载元素同述;本批实现=对齐现行条款 | `design/art/art-bible.md` §6.2/§7.5/§2.8 |
| F11 | 图鉴内容在册挂起:奖励格图鉴柜台已随配比预设批整体挂起冻结(2026-10-05 用户令,06 在册),重启须用户另行发令——本批只改入口不激活内容 | `design/gdd/systems/跨局循环/生涯.md:63` |
| F12 | 贴图重构批搁置未废:营地与元进度件终稿置换(06:21-27)等交互重做定形后再议(澄清③1/§⑧5)——本批不清偿该债;四柜台拆解后原「柜台场景贴图」债对象变化,随该批重新评估 | `design/gdd/06-待办清单.md:21-27`;`需求澄清-营地交互重做-2026-10-18.md` §③1/§⑧5 |
| F13 | 营地底栏「最近冒险」条带与生涯历史回看同源(同一份 `recent_summaries`),其去留=生涯.md P6 待裁点(期二必答)——CampOverlay 拆解后该条带无处挂,**Q4 已裁=甲**(随四柜台拆解撤下,逐局历史回看归生涯,生涯.md P6 同批注销) | `camp_overlay_view.gd:186-189`/:536-549;`生涯.md:69` |
| F14 | 素材线纪律:`artwork/` 不入 git(`.gitignore:51` 忽略行)——批次 worktree 内无该目录、主工作区 D:/code/g-plan2 实体存在(在途工作区);涉素材脚本须传主区绝对路径否则静默 0 张;素材替换前先归档旧档;终稿归 `game/assets/art/scene/`,消费常量 `UiKit.TEX_CAMP_*`(asset-path-map.md §3/art-assets-checklist.md §3.5) | `AGENTS.md:32`;`.gitignore:51`;art-bible §8.3 归档纪律 |
| F12 | 贴图重构批搁置未废:营地与元进度件终稿置换(待办清单.md 在册条)等交互重做定形后再议(澄清③1/§⑧5)——本批不清偿该债;四柜台拆解后原「柜台场景贴图」债对象变化,随该批重新评估(原 `design/gdd/06-待办清单.md` 路径已迁仓库根) | `待办清单.md`(仓库根);`需求澄清-营地交互重做-2026-10-18.md` §③1/§⑧5 |
| F13 | 营地底栏「最近冒险」条带与生涯历史回看同源(同一份 `recent_summaries`),其去留=生涯.md P6 待裁点(期二必答)——CampOverlay 拆解后该条带无处挂,**Q4 已裁=甲**(随四柜台拆解撤下,逐局历史回看归生涯,生涯.md P6 同批注销) | `camp_overlay_view.gd`(底栏 :214 起/`_rebuild_recent` :634);`生涯.md:69` |
| F14 | 素材线纪律:`artwork/` 不入 git(`.gitignore:54` 忽略行)——批次 worktree 内无该目录、主工作区 D:/code/g-plan2 实体存在(在途工作区);涉素材脚本须传主区绝对路径否则静默 0 张;素材替换前先归档旧档;终稿归 `game/assets/art/scene/`;**消费常量 `UiKit.TEX_CAMP_*` 尚不存在**(全仓 grep 零命中,仅 art-assets-checklist.md:135 文字提及)——本批在 `ui_kit.gd` 新增十键(T1 先定常量、素材名跟常量走,防断链;2026-11-01 裁) | `AGENTS.md:32`;`.gitignore:54`;art-bible §8.3 归档纪律;art-assets-checklist.md §3.5 |
| F15 | 美术硬约束继续有效(澄清③4):明亮轻快像素风、**48 色板、HSV 300°–345° 禁入带、语义色纪律、WCAG ≥4.5:1**——全局约束,不在本讨论范围翻案 | `需求澄清-营地交互重做-2026-10-18.md` §③4 |
| F16 | 像素级规格已定(Q6 甲过渡口径+渠道 B):1280×800 工程档、烛光黄 #F6C667/木色 #8A5A34、2× 源档出图最近邻降采样(art-assets-checklist.md §3.5);终档归样张批冻结清单后置换(反哺点);卡内禁自定像素规格数值 | `design/art/art-assets-checklist.md` §3.5;`design/art/art-bible.md` §6.2 |
| F17 | 机检工具面:**截屏级 48 色计数不在役**;在役=`tools/palette_check.py`(art-bible §8.3①挂点——48 设计色板令牌+新增色双门)+`tools/standee_asset_check.py`(像素画单张 32 色冻结执法);`tools/render_gate.sh`(shot_probe 七屏→shot_checker 语义断言;golden 段=棋盘三张+menu_boot 四张,**无 camp golden**——shot_checker.py:143 明载「camp/level 走骨架+目检清单」);shot_checker 段=屏级骨架/文字 WCAG/明度三档带/文字禁压纹理/文字在判据 | `tools/palette_check.py`;`tools/standee_asset_check.py`;`tools/render_gate.sh`;`tools/shot_checker.py:143` |
| F18 | 参照物已补(2026-10-23)=`artwork/2026-10-23-营地场景/Gemini_Generated_Image_ykrc0nykrc0nykrc.jpg`,已进 camp-scene-prompts.md §1 风格参考图表登记为「作者提供的参照物」,与纵深稿对过表、围墙结构冲突已呈报;参照物只是参考图,保留 `artwork/`、不入 git、不入游戏目录 | `design/art/camp-scene-prompts.md` §1;`artwork/2026-10-23-营地场景/` |
| F19 | 10 层拆分现行口径(覆盖旧 8/9 张):art-bible v3.24 §6.2/camp.md v2.2/camp-scene-prompts.md v1.2/asset-path-map.md §3/art-assets-checklist.md §3.5 同步;围墙从背景层独立成 z1、门洞小路画在 z0 bg 层;围墙落盘细条 (0,240,1280,70) 不做全幅 | `design/art/art-bible.md` §6.2;`design/gdd/ux/camp.md:27-31` |
| F19 | 10 层拆分现行口径(覆盖旧 8/9 张):art-bible v3.31 §6.2/camp.md v2.2/camp-scene-prompts.md v1.2/asset-path-map.md §3/art-assets-checklist.md §3.5 同步;围墙从背景层独立成 z1、门洞小路画在 z0 bg 层;围墙落盘细条 (0,240,1280,70) 不做全幅 | `design/art/art-bible.md` §6.2;`design/gdd/ux/camp.md:27-31` |
| F20 | 样张过审与终稿裁定(2026-10-31 用户裁定):保留全部待选稿、各素材第一张样本稿为终稿、「目前我看每个都还可以接受」,用户准备在游戏内做最终审核;终稿此刻=原始候选图本身,未做去底/擦水印/降采样/裁细条等任何后处理(用户 2026-10-31 令只生成素材不做后续工序);清单=`artwork/candidates/_终稿裁定-2026-10-31.txt` | `artwork/candidates/_终稿裁定-2026-10-31.txt` |
## 3. 设计契约(红线,违反 = L3 停机呈报)
@@ -138,7 +138,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
🔴 **契约 3 · 大门两分支**(裁决 5,Q5 已裁=甲):有未完冒险→ConfirmDialog 三出口「继续上一段冒险/开始新冒险/取消」;无→「是否开始新冒险:是/否」弹窗。**点大门永远先出确认,不直接进冒险**;cancelled 恒=无变更退回营地;弹窗类=ConfirmDialog(三钮化+消费方编排=T4,卡③ 契约 3 同款互指)。
🔴 **契约 4 · 已实现界面只改挂不重做+presenter 承接面**:锻造工坊/秘宝匣/天赋树/图鉴/生涯五界面「界面已实现 · 入口改挂」——功能、行板、写口链路零重设计(拆解=承载面迁移)。**presenter 改造=承载面迁移的一部分**:`camp_shop_presenter.gd` 持 `camp_overlay` 句柄(:10)、七条写口尾全部 `camp_overlay.refresh_camp(camp_data())`(:85/93/105/117/129/142/154/168)、`CampFlowContext` 持类型化字段(camp_flow_context.gd:10/22/29)、open/close 编排(:41-56)——**写口语义零改≠presenter 文件零改**:投影目标改线(五界面/场景根)、写口尾刷新改路由、open/close 编排随入口从 campbtn 迁位到卡③ 进档动作、ctx 字段语义订正、探针缝保位(main.gd:1163-1171)。改造纪律:`on_camp_attune_toggle`(:98-106)尾只 refresh_camp、不调 `_refresh_presentations`(与其余六口不一致)——**保持原样,禁「顺手统一」防行为漂移**。
🔴 **契约 4 · 已实现界面只改挂不重做+presenter 承接面**:锻造工坊/秘宝匣/天赋树/图鉴/生涯五界面「界面已实现 · 入口改挂」——功能、行板、写口链路零重设计(拆解=承载面迁移)。**presenter 改造=承载面迁移的一部分**:`camp_shop_presenter.gd` 持 `camp_overlay` 句柄(:10)、写口尾刷新调用八处(七口+`on_camp_buy` 双尾)全部 `camp_overlay.refresh_camp(camp_data())`(:82/90/102/114/126/139/151/165,2026-11-01 复核实数)、`CampFlowContext` 持类型化字段(camp_flow_context.gd:10/22/29)、open/close 编排(:41-56)——**写口语义零改≠presenter 文件零改**:投影目标改线(五界面/场景根)、写口尾刷新改路由、open/close 编排随入口从 campbtn 迁位到卡③ 进档动作、ctx 字段语义订正、探针缝保位(组合根薄委托缝,T0 复核现役行号——原引 main.gd:1163-1171 超出文件实长、系旧盘面残留)。改造纪律:`on_camp_attune_toggle` 口尾(:102)只 refresh_camp、不调 `_refresh_presentations`(与其余口不一致)——**保持原样,禁「顺手统一」防行为漂移**。
🔴 **契约 5 · 图鉴内容保持挂起**:只改入口(布告板),不激活图鉴内容(F11:2026-10-05 用户令冻结,重启须用户发令);布告板点开的图鉴=现行占位态(T3 定义占位壳),不是禁用态(camp.md v2.2)。
@@ -158,7 +158,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
## 4. 范围边界
**做**:场景屏+10 层场景素材铺底+七物件命中区与点选交互(反馈细节按 §0.3 P3 设计稿、四态规格按 camp.md v2.2);四柜台拆解与五界面入口改挂(含 presenter 承接面改造);大门动线两分支+断点弹窗移位;篝火回主菜单小动线(带二次确认);场景屏顶栏预留位;断言/探针迁移;反哺点清单。
**做**:场景屏+10 层场景素材铺底+七物件命中区与点选交互(四态规格按 camp.md v2.2——§0.3 P3 已销号=camp.md v2.2 四态规格即设计稿);四柜台拆解与五界面入口改挂(含 presenter 承接面改造);大门动线两分支+断点弹窗移位;篝火回主菜单小动线(带二次确认);场景屏顶栏预留位;断言/探针迁移;反哺点清单。
**不做**(明示去向):
- **不做贴图重构批**(澄清③1):柜台场景贴图/钥石顶栏图标/永久强化条目图标的达标化置换——搁置未废,待交互重做完形后重新评估立项(F12);
@@ -167,25 +167,26 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- 不改冒险屏/选路屏/战斗屏本体(F7 已实现不动);
- 不做整屏样张闸、不替代样张批、不新建 camp golden(§0.5/契约 10);
- 局内状态面板零改动(契约 6);
- 素材后处理与入库属实现段 T1/`asset-ingest` 第 3 步、开工另发令(§0.3-4),不属写卡门;
- 素材后处理与入库属实现段 T1/`asset-ingest` 第 3 步(§0.3-4 授权形态:/iter 开工令即含授权),不属写卡门;
- ESC 浮窗与顶栏 HUD 实现 → 卡⑤(本卡只留顶栏位+定义刷新单口);
- 选档界面/主菜单/数据层 → 卡③/卡②/卡①。
## 5. 关联文档同步面
> **现行口径**:实现批禁写 `design/` 整目录与根规则文件,唯一可写目录=`reports/`(+`game/` 实施本体);设计权威文档(GDD/art-bible)与机械面文档(roadmap/体验验证清单/README)对代码实现批一律不直改,逐条列进 `reports/iter<N>-反哺点-<日期>.md`,用户裁决后归文档批落文;本卡全表义务格一律为「反哺点 · 列清单」。camp.md v2.2/art-bible v3.24 §6.2 已先行落文 10 层新形态,本批实现=对齐现行条款,反哺义务为**实装状态回填+目检结论**,非形态改版。美术素材产物按素材线纪律落 `artwork/`(在途)→ `game/assets/art/scene/`(终稿),不随 git 合并(F14)。
> **现行口径**:实现批禁写 `design/` 整目录与根规则文件,唯一可写目录=`reports/`(+`game/` 实施本体);设计权威文档(GDD/art-bible)与机械面文档(roadmap/体验验证清单/README)对代码实现批一律不直改,逐条列进 `reports/iter<N>-反哺点-<日期>.md`,用户裁决后归文档批落文;本卡全表义务格一律为「反哺点 · 列清单」。camp.md v2.2/art-bible v3.31 §6.2 已先行落文 10 层新形态,本批实现=对齐现行条款,反哺义务为**实装状态回填+目检结论**,非形态改版。美术素材产物按素材线纪律落 `artwork/`(在途)→ `game/assets/art/scene/`(终稿),不随 git 合并(F14)。
| 文档 | 关联点 | 同步义务 |
|---|---|---|
| `design/gdd/ux/camp.md` v2.2 | §定位/§场景构成与七物件绑定/§点选反馈/§顶栏/§叠层与开合:新形态已落文,本批实现后回填实装状态、样张过审留痕、实机目检结论 | 反哺点 · 列清单(实现批禁写 `design/`) |
| `design/art/art-bible.md` v3.24 | §6.2 营地场景 10 层口径已落文;§7.5 营地界面画法、§2.8 承载元素、§1.4 营地屏整屏样张排队行注记(本批不跑样张闸) | 反哺点 · 列清单(权威面零直改,过用户裁决后归文档批) |
| `design/art/art-bible.md` v3.31 | §6.2 营地场景 10 层口径已落文;§7.5 营地界面画法、§2.8 承载元素、§1.4 营地屏整屏样张排队行注记(本批不跑样张闸) | 反哺点 · 列清单(权威面零直改,过用户裁决后归文档批) |
| `design/art/camp-scene-prompts.md` v1.3 | §5「待裁」节五点已裁断(2026-11-01 用户裁:①帐篷追认现值②门扇追认现值③布告板 (845,268,130,140)④描边奶油色追认⑤环境子板色值不前置、T1 量化后按 §4.1 登记)——**已落文(2026-11-01 本会话文档批):camp-scene-prompts.md v1.3 §5 改已裁+art-bible.md v3.31 §6.2 待裁行改已裁,义务了结** | 已落文 · 义务了结 |
| `design/gdd/systems/跨局循环/生涯.md` | :69 与营地边界表(条带 `_rebuild_recent` 随 CampOverlay 拆解去向=Q4 已裁甲);:63 图鉴挂起条(入口改挂注记) | 反哺点 · 列清单 |
| `design/gdd/08-Alpha版本标准.md` | 「三 · 界面」营地行(:104)形态注记改写——状态列可改;注记改写列反哺点;增删行须用户裁(三卡统一口径) | 反哺点 · 列清单 |
| `design/gdd/06-待办清单.md` | 本卡登记条;UI 贴图重构批条(:21-27)注记「四柜台对象变化、重评待交互重做完形」;图鉴挂起条注记 | 反哺点 · 列清单(实现批禁写待办清单) |
| `design/gdd/07-体验验证清单.md` | 新增实机目检条(场景观感/七物件辨识与点选反馈/大门两分支/篝火动线)——登记不阻塞 | 反哺点 · 列清单(实现批禁写体验验证清单,入册条目由文档批落) |
| `roadmap.md` | 迭代行编入/状态刷新 | 反哺点 · 列清单(实现批禁写 roadmap) |
| `game/tests/README.md` | 能力行入表(§7 对照表「能力行」列定名) | 写卡段定名(本卡只写卡内能力行文字,README 由主 Agent 统一收口);实现批只核对名实一致,禁写 |
| `game/presentation/*`、`game/smoke/*`、`game/tests/**`(含 forge_workbench_test 迁移面)、`game/tests/golden/*`(仅棋盘/menu 既有基线复跑)、`game/assets/art/scene/*`(10 层终稿) | 实现/断言/终稿素材本体 | 实施本体(可写) |
| `game/tests/README.md` | 能力行入表(§7 对照表「能力行」列定名) | **已入表**(旅程段六,本卡七条新增能力行在役;v3.1 V14 用例锚随批补)——实现批只核对名实一致,禁写 |
| `game/presentation/*`、`game/probe/*`、`game/tests/**`(含 forge_workbench_test 迁移面)、`game/tests/golden/*`(仅棋盘/menu 既有基线复跑)、`game/assets/art/scene/*`(10 层终稿) | 实现/断言/终稿素材本体 | 实施本体(可写) |
| `tools/shot_checker.py` | camp 取样面语义断言段(文字 WCAG/明度三档带/文字禁压纹理/文字在判据扩 camp) | 实施本体(可写,属工具/测试面) |
## 卡前·文件结构与职责(T0 前)
@@ -194,9 +195,9 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| 文件 | 职责 | 改动类型 | 落位 | 对照表行 |
|---|---|---|---|---|
| `game/presentation/views/camp_scene_view.gd` | 交互式营地场景图视图:10 层纹理铺底(z0–z6)+七物件命中区+点选反馈四态+名牌 | 行为改动 | 新建 | V1/V2/V9/V10 |
| `game/data/tables/camp_scene_layout.tres` | 10 层贴图矩形/命中矩形/z 序/门洞缺口坐标(数值单一事实源 `data/tables/`,坐标最终值随 §0.3 代码段开工门裁定落入,本卡不硬定) | 行为改动 | 新建 | V1/V2/V9 |
| `game/presentation/views/camp_refresh_bus.gd` | 营地刷新单口:各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成(卡⑤ 同口挂接) | 行为改动 | 新建 | V8/B1 |
| `game/presentation/views/camp_scene_view.gd` | 交互式营地场景图视图:10 层纹理铺底(z0–z6)+七物件命中区+点选反馈四态+名牌(节点选型=Control 根+TextureRect 层+子节点序即 z 序,T2) | 行为改动 | 新建 | V1/V2/V9/V10 |
| `game/data/tables/camp_scene_layout.tres` | 10 层贴图矩形/命中矩形/z 序/门洞缺口坐标(数值单一事实源 `data/tables/`;schema 类+表现域容器类型化成员,先例 battle_layout.tres=`core/config_db.gd`/`core/config_domain_presentation.gd`;加载期 fail-fast 校验命中区几何,T2) | 行为改动 | 新建 | V1/V2/V9/V14 |
| `game/presentation/views/camp_refresh_bus.gd` | 营地刷新单口:各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成(卡⑤ 同口挂接;头注自陈「非 autoload、非信号广播——组合根单点推送」) | 行为改动 | 新建 | V8/B1 |
| `game/presentation/views/forge_view.gd` | 锻造工坊独立可开界面(自 CampOverlay `_forge_workbench` 抽出,界面逻辑复用) | 行为改动 | 新建(抽出) | V4/B2 |
| `game/presentation/views/treasure_view.gd` | 秘宝匣独立可开界面(自 `_ws_scroll`/`_workshop_holder` 抽出) | 行为改动 | 新建(抽出) | V4/B2 |
| `game/presentation/views/tree_view.gd` | 天赋树独立可开界面(自 `_tree_holder` 抽出) | 行为改动 | 新建(抽出) | V4/B2 |
@@ -205,17 +206,20 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| `game/presentation/views/camp_overlay_view.gd` | 四柜台覆盖层拆解后退役(无消费面即删,吸收即死) | 行为改动 | 退役 | V4/V9/B2 |
| `game/presentation/views/career_overlay.gd` | 生涯页入口自主菜单改挂帐篷旁笔记本(本体零改,返回钮文案禁含「营地」) | 行为改动 | 复用既有(改入口) | V5 |
| `game/presentation/views/confirm_dialog.gd` | 三钮化(继续上一段冒险/开始新冒险/取消) | 行为改动 | 复用既有(改) | V3/B1 |
| `game/presentation/presenters/camp_shop_presenter.gd` | 投影目标改线+七写口尾刷新改路由+open/close 编排迁位(契约 4) | 行为改动 | 复用既有(改) | V4/V8/B1 |
| `game/presentation/core/camp_flow_context.gd` | 类型化字段语义订正 | 行为改动 | 复用既有(改) | V4/B1 |
| `game/presentation/camp_shop_presenter.gd` | 投影目标改线+写口尾刷新改路由+open/close 编排迁位(契约 4;路径订正——v3.0 写 `presenters/` 子目录不存在) | 行为改动 | 复用既有(改) | V4/V8/B1 |
| `game/presentation/camp_flow_context.gd` | 类型化字段语义订正(路径订正——v3.0 写 `core/` 子目录不存在) | 行为改动 | 复用既有(改) | V4/B1 |
| `game/presentation/ui_camp_modals.gd` | 营地域模态句柄子容器(卡⑥ 前置建件;本卡 T3 落场景根+四子界面五枚句柄,卡⑤ T1 落浮窗句柄;寻址 `ui.modals.camp.X` 二层) | 行为改动 | 新建(卡⑥) | V1/V8 |
| `game/presentation/ui_modals.gd` | 本卡零句柄新增(营地域句柄落 ui_camp_modals.gd 子容器,2026-11-01 用户裁);类变量 14 不升(S13 预期,见「实现规范预算·存量触面一查」) | 非行为改动(卡④ 段零改) | 复用既有 | V1/V8 |
| `game/presentation/main_ui_build.gd` | 新层实例化装配点:各 `.new()`+装配尾 `apply_layer_contract(root)`(先例 :105/:121/:242);五句柄赋值经 `modals.camp.X` 子容器 | 行为改动 | 复用既有(改) | V1/V8 |
| `main.gd`(组合根) | 断点弹窗移位+ConfirmDialog 三钮消费方编排+营地入口改挂+篝火动线+开层互斥/关层收闭 | 行为改动 | 复用既有(改) | V3/V4/V7/B1 |
| `game/assets/art/scene/scene_camp_*.png`(10 张) | 10 层场景素材终稿(asset-ingest 第 3 步拷入,命名对齐 `UiKit.TEX_CAMP_*`) | 非行为改动(美术资源) | asset-ingest | V1/V10 |
| `game/smoke/shot_probe.gd` | camp 截图路径改场景图+收闭改接篝火动线 | 非行为改动(测试面) | 复用既有(改) | V1 |
| `game/smoke/ui_click_probe.gd` | 七物件可达+大门两分支+断点确认段改走大门+写口链迁移+深员改指 | 非行为改动(测试面) | 复用既有(改) | V2/V3/V4 |
| `game/smoke/ui_probe.gd` | 营地入口/余额/秘宝匣深员改指新界面 | 非行为改动(测试面) | 复用既有(改) | V4 |
| `game/smoke/screen_flow_probe.gd` | 营地键新语义+回退段改新场景根 closed/篝火动线 | 非行为改动(测试面) | 复用既有(改) | V7 |
| `game/smoke/ui_struct_probe.gd` | 反参照结构禁词+生涯段驱动改接+图鉴占位锚定 | 非行为改动(测试面) | 复用既有(改) | V5/V6/V9 |
| `game/smoke/ui_contrast_probe.gd` | 营地令牌段核签(CAMP_HEADER_BAND/CAMP_ROW_PLATE 消费判定) | 非行为改动(测试面) | 复用既有(改) | V10 |
| `game/smoke/smoke_all.gd` | V2 实例化/V3g 模态开合改指+`camp_overlay_view.gd` 源码断言段 :6469-6475 重写或注销 | 非行为改动(测试面) | 复用既有(改) | V4/B2 |
| `game/assets/art/scene/scene_camp_*.png`(10 张) | 10 层场景素材终稿(asset-ingest 第 3 步拷入,命名对齐 `UiKit.TEX_CAMP_*`——常量十键本批在 `ui_kit.gd` 新增) | 非行为改动(美术资源) | asset-ingest | V1/V10 |
| `game/probe/shot_probe.gd` | camp 截图路径改场景图+收闭改接篝火动线 | 非行为改动(测试面) | 复用既有(改) | V1 |
| `game/probe/ui_click_probe.gd` | 七物件可达+大门两分支+断点确认段改走大门+写口链迁移+深员改指 | 非行为改动(测试面) | 复用既有(改) | V2/V3/V4 |
| `game/probe/ui_probe.gd` | 营地入口/余额/秘宝匣深员改指新界面 | 非行为改动(测试面) | 复用既有(改) | V4 |
| `game/probe/screen_flow_probe.gd` | 营地旗标新语义+回退段改新场景根 closed/篝火动线 | 非行为改动(测试面) | 复用既有(改) | V7 |
| `game/probe/ui_struct_probe.gd` | 反参照结构禁词+生涯段驱动改接+图鉴占位锚定+camp 场景层序段(V1 补立) | 非行为改动(测试面) | 复用既有(改) | V1/V5/V6/V9 |
| `game/probe/ui_contrast_probe.gd` | 营地令牌段核签(CAMP_HEADER_BAND/CAMP_ROW_PLATE 消费判定) | 非行为改动(测试面) | 复用既有(改) | V10 |
| `game/tests/system_test.gd`+`game/tests/unit/presentation/theme_projection_test.gd` | V2 实例化/V3g 模态开合改指(system_test :67-68/:520-541)+四柜台等高源码断言段重写或注销(theme_projection_test :710-717)——`game/smoke/smoke_all.gd` 已退役,v3.0 旧位点作废 | 非行为改动(测试面) | 复用既有(改) | V4/B2 |
| `game/tests/unit/presentation/forge_workbench_test.gd` | 随锻造视图抽出同迁(断言零改、只换承载类) | 非行为改动(测试面) | 复用既有(改) | V4/B2 |
| `tools/shot_checker.py` | camp 取样面语义断言段 | 非行为改动(工具/测试) | 复用既有(改) | V10 |
| `game/tests/README.md` | 能力行入表(四列:能力|系统|真实入口|代表用例) | 非行为改动(文档,写卡段) | 主 Agent 统一收口 | 全表能力行 |
@@ -230,8 +234,9 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| 新件 | 复用面(grep 同职责实现) | 唯一面(grep 同名同义标识符) | 分层与交互面(调用路径+跨层出口) |
|---|---|---|---|
| `camp_scene_view.gd` | 盘上视图件=CampOverlay(四柜台覆盖层,形态本批退役、与场景图形态正交,反参照禁复用)、CareerOverlay(生涯全屏页,职责不同)、camp_screen.gd(屏壳)。**无**「场景图铺底+七物件命中区+点选反馈」同职责实现 → 不能复用,须新建视图件;屏壳复用既有 `camp_screen.gd` 改造,不新建屏壳 | grep `camp_scene`/`scene_camp` 视图标识符盘上无第二处;素材消费常量 `UiKit.TEX_CAMP_*` 单点(asset-path-map.md §3),不另立路径常量 | 呈现层视图,零 Core 写调用;命中区 `_gui_input()` → `object_pressed(key: String)` 信号 → 组合根 → 开界面/写口;10 层装载经 `UiKit.load_tex()`(exists 守卫、禁 preload);不直连 MetaProgression |
| `camp_scene_layout.tres` | grep `game/data/tables/` 无营地坐标表;坐标草案在 artwork HTML(不入 git)与 camp-scene-prompts §2.3(设计文档,非运行时数据源)。数值单一事实源要求落 `data/tables/` → 新建 | 坐标事实只此一处;禁在 `camp_scene_view.gd`/探针/T 卡各写一份坐标字面量(契约 8 禁自定规格、数值单点) | 视图启动时经 ConfigDB 单点读取;坐标最终值随 §0.3 代码段开工门(camp-scene-prompts §5 五裁点)裁定落入,本卡只定结构键不定值 |
| `camp_scene_layout.tres` | grep `game/data/tables/` 无营地坐标表;坐标草案在 artwork HTML(不入 git)与 camp-scene-prompts §2.3(设计文档,非运行时数据源)。数值单一事实源要求落 `data/tables/` → 新建 | 坐标事实只此一处;禁在 `camp_scene_view.gd`/探针/T 卡各写一份坐标字面量(契约 8 禁自定规格、数值单点) | 视图启动时经 ConfigDB 单点读取(schema 类+表现域容器类型化成员,先例 battle_layout.tres);坐标值按 §0.3-3 五裁点裁断(2026-11-01)由 T2 定值落入 |
| `camp_refresh_bus.gd` | grep 现役 `refresh_camp` 绑定 CampOverlay 句柄(F5),随四柜台形态**退役**(卡⑤/§0.5 明确「非已退役的 refresh_camp」);新单口须同时刷新现开界面与场景根 HUD、职责不同 → 不能复用,须新建 | grep `camp_refresh`/刷新单口类标识符盘上无第二处;卡⑤ HUD 刷新挂此单口,不另立第二口 | 组合根拆件:七写口尾 → `CampRefreshBus.refresh(camp_data: Dictionary) -> void` → 现开界面刷新+场景根 HUD 刷新;一次性刷新,不新增写口语义(契约 4) |
| `ui_camp_modals.gd`(卡⑥ 建件,本卡落五枚) | grep 句柄分域容器无先例;`UiModals` 本体=按技术角色平铺的注册面容器(职责不同)、`ModalFlow`=调度器非句柄册 → 不能复用,须新建(卡⑥ 已立卡) | grep `UiCampModals`/`ui_camp_modals` 无第二处;营地句柄寻址唯一落点 | RefCounted 不挂树;`main_ui_build` 构造注入 `ui_modals.camp`;`register_layer`/LAYER_CONTRACT/树序零触碰(句柄册结构≠树结构) |
| `forge_view.gd`/`treasure_view.gd`/`tree_view.gd`/`codex_view.gd` | 界面逻辑**已实现**于 CampOverlay 内(F4:`_forge_workbench`/`_ws_scroll`/`_tree_holder`/图鉴占位);本批是抽出承载壳、**复用既有界面逻辑**(行板/写口链路零重设计,契约 4),非从零新写 → 新建壳、迁移逻辑 | grep `forge_view`/`treasure_view`/`tree_view`/`codex_view`/`_forge_rows`/`_forge_workbench` 盘上无第二处定义;`forge_workbench_test.gd:33` 消费 `_forge_rows`/`_forge_workbench`,抽出后同迁,断言零改只换承载类 | 各视图发意图信号(`buy_requested`/`tree_buy_requested`/`forge_*`)→ 组合根 → CampShopPresenter → MetaProgression 写口(Core 零 UI 不破);五界面各带返回/关闭钮;开合走模态栈单口、层序入 `LAYER_CONTRACT` |
### 非新增件说明
@@ -251,12 +256,13 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
|---|---|---|---|---|
| `camp_scene_view.gd` | 预期 ≤8:`setup_layers()`/`bind_hotspots()`/`set_feedback_state()`/`get_hotspot_rect()`/`set_hud_band()`/`_on_hotspot_input()` 等,远低 ≤20 | 预期 ≤12:10 层纹理句柄+命中区表+反馈态+名牌节点;收编进内聚结构,低 ≤15 | 无自存量文件迁入函数 | 三查通过;T0 实测实数回填,超阈值即拆件或砍面(砍面=退设计段裁职责) |
| `camp_refresh_bus.gd` | 预期 ≤3:`refresh()`/`bind_targets()`/`_dispatch()`,远低 ≤20 | 预期 ≤4:现开界面句柄+场景根 HUD 句柄+投影数据,低 ≤15 | 自 `camp_shop_presenter.gd` 迁入刷新路由;先查 `tools/gdstyle_baseline.txt` 源函数键,branches/returns/函数长超默认登记值的迁移即拆函数(拆分不改语义,不算迎合机检) | 三查通过;迁入后补跑 S13,不凭旧读数放行 |
| `ui_camp_modals.gd`(卡⑥ 建件) | 预期 ≤1(`_init`) | 预期 5(本卡 T3 落五枚;卡⑤ 落满 6),低 ≤15 | 无迁入函数 | 三查通过(卡⑥ 卡面已立预算) |
| 四界面 view(各) | 各预期 ≤6:`open()`/`close()`/`bind_rows()`/`_on_action()` 等,远低 ≤20 | 各预期 ≤10:复用既有行板成员(`_forge_workbench`/`_ws_scroll`/`_tree_holder`),低 ≤15 | 自 `camp_overlay_view.gd` 迁入对应界面函数;逐个对 `tools/gdstyle_baseline.txt` 源键,超登记值即拆函数 | 三查通过;抽出后实测实数回填 |
| `camp_scene_layout.tres` | 不适用(数据件,无方法) | 不适用(结构键,非类变量) | 不适用(无迁入函数) | 结构键唯一,坐标值随 §0.3 开工门落入 |
| `camp_scene_layout.tres` | 不适用(数据件,无方法) | 不适用(结构键,非类变量) | 不适用(无迁入函数) | 结构键唯一,坐标值按 §0.3-3 五裁点裁断(2026-11-01)由 T2 定值落入 |
### 存量触面一查
本卡触碰存量文件:`camp_screen.gd`/`camp_shop_presenter.gd`/`camp_flow_context.gd`/`confirm_dialog.gd`/`career_overlay.gd`/`camp_overlay_view.gd`(退役)/`main.gd`/探针七件/`forge_workbench_test.gd`。**T0 读数步骤写明「S13 键较 `tools/gdstyle_baseline.txt` 登记值只降不升」的预期与达成手段**:CampOverlay 拆解=删四柜台代码(键只降);`camp_shop_presenter.gd` 改造=投影目标改线、不新增公开方法/成员;`main.gd` 组合根=断点弹窗移位+消费方编排(新增私有状态量一枚,S13 类级键只降不升);探针改接=替换断言段、不新增扫描面键。任何 S13 键被迫上升=§8 L3 停卡。
本卡触碰存量文件:`camp_screen.gd`/`camp_shop_presenter.gd`/`camp_flow_context.gd`/`confirm_dialog.gd`/`career_overlay.gd`/`camp_overlay_view.gd`(退役)/`main.gd`/`ui_modals.gd`/`ui_registry.gd`/`main_ui_build.gd`/探针七件/`forge_workbench_test.gd`。**T0 读数步骤写明「S13 键较 `tools/gdstyle_baseline.txt` 登记值只降不升」的预期与达成手段**:`ui_modals.gd` 类变量 14→14(−平铺 `camp_overlay`+camp 容器成员由卡⑥ 建件承担——营地域句柄一律落 `ui_camp_modals.gd` 子容器,2026-11-01 用户裁,禁平铺);`ui_registry.gd` +camp 二层解析分支(分支数 ≤8);`main_ui_build.gd` 装配行改指(方法面微增,T0 实测对账);CampOverlay 拆解=删四柜台代码(键只降);`camp_shop_presenter.gd` 改造=投影目标改线、不新增公开方法/成员;`main.gd` 组合根=断点弹窗移位+消费方编排(新增私有状态量一枚,S13 类级键只降不升);探针改接=替换断言段、不新增扫描面键。任何 S13 键被迫上升=§8 L3 停卡。
### 指标例外通道
@@ -268,20 +274,22 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
### T0 事实坐实与设计输入齐备检查(≈0.5d)
- 取用:待办清单条目状态(文档设计已闭合+美术设计已闭合+已写卡)、§0 重立凭据、art-bible v3.24 §6.2、camp.md v2.2、camp-scene-prompts.md v1.2、`_终稿裁定-2026-10-31.txt`、布局草案-1280x800.html。
- 取用:待办清单条目状态(文档设计已闭合+美术设计已闭合+已写卡)、§0 重立凭据、art-bible v3.31 §6.2、camp.md v2.2、camp-scene-prompts.md v1.2、`_终稿裁定-2026-10-31.txt`、布局草案-1280x800.html。
- [ ] 复核 §2 F1–F20 全部 `file:line` 行号;四柜台界面清单与探针面清单(F8)逐点过——漂移即停卡核对,不猜行号。
- [ ] 设计输入齐备检查:Q1(美术渠道=B 已裁)/Q2(参照物已补)/Q4(条带=甲)/Q5(三钮=甲)/Q6(过渡口径=甲)/Q8(出稿方式=甲)状态逐项核为已裁;**代码段开工门**(§0.3:P3/P5/camp-scene-prompts §5 五裁点/素材后处理入库)状态逐项核——未销号项对应的任务段禁开工(缺输入=呈报,禁批内自补)。
- [ ] 设计输入齐备检查:Q1(美术渠道=B 已裁)/Q2(参照物已补)/Q4(条带=甲)/Q5(三钮=甲)/Q6(过渡口径=甲)/Q8(出稿方式=甲)状态逐项核为已裁;**代码段开工门**(§0.3 四项)状态逐项核为已裁断销号(2026-11-01)——若发现裁断实况反转=L3 停机呈报,禁批内自补。
- [ ] **新场景根句柄名+四子界面(forge/treasure/tree/codex view)+场景层 `LAYER_CONTRACT` 槽名定稿**(显式验收点,2026-11-01 双审补立——卡⑤ 只消费不改名);句柄落 `ui_camp_modals.gd` 子容器(卡⑥ 前置建件,寻址 `ui.modals.camp.X` 二层)+`main_ui_build.gd` 装配点(卡⑤ v1.5 文件表同步列)。
- [ ] **跨卡排序登记**:camp 断言基线建立在无 HUD 场景屏(本卡 T5);卡⑤ T2 落 HUD 后由卡⑤ 限 HUD 区显式更新+呈报(卡⑤ v1.4 §0 已对齐)。
- [ ] 规格清单依赖核检:样张批规格清单产出时点 vs 本批排期——按 Q6 甲过渡口径执行(1280 工程档+烛光黄/木色,终档置换归反哺点),禁 T0 现场发挥。
- [ ] 跑 `bash tools/gdstyle.sh game` 开发期局部读数,记录 `camp_screen.gd`/`camp_shop_presenter.gd`/`main.gd` 当前行数、类变量、公开方法数;登记 S13 基线键(预期只降不升)。本步为最窄确认,不构成验收。
- [ ] 跑 `bash tools/static_scan.sh` 开发期局部读数(gdstyle 键=S13,闸件 `tools/gdstyle_gate.py`——原 `tools/gdstyle.sh` 不存在,v3.1 订正),记录 `camp_screen.gd`/`camp_shop_presenter.gd`/`main.gd` 当前行数、类变量、公开方法数;登记 S13 基线键(预期只降不升)。本步为最窄确认,不构成验收。
- [ ] 与卡③ 对齐 ConfirmDialog 消费方编排机制(T4 互指)、与卡⑤ 对齐顶栏位与营地刷新单口(§0.5 跨卡接口)。
- 产出:基线 commit、S13 实数、设计凭据核对表、代码段开工门未销号清单;后续卡只按本卡契约施工。
### T1 场景素材终稿录入(asset-ingest 第 3 步,≈1–1.5d,用户另发令)
### T1 场景素材终稿录入(asset-ingest 第 3 步,≈1–1.5d;后处理授权随 /iter 开工令,§0.3-4)
- 取用:P1=B 渠道已裁(2026-10-22)、P4 参照物已补(2026-10-23)、Q6 甲过渡口径已裁(1280×800+烛光黄/木色、2× 源档最近邻降采样)、`_终稿裁定-2026-10-31.txt`(各素材第一张样本稿为终稿、保留全部待选稿)、camp-scene-prompts.md §2–§4。
- [ ] 按终稿裁定清单核对十层指定文件(z0 bg/z1 wall/z2–z6 八物件层),确认各取第一张样本稿为终稿、其余候选保留不删。
- [ ] 用户发令后做后处理:去底转透明(bg 不去底、全幅不透明)/擦通道水印/降采样到 1280×800(整数倍)/围墙层裁细条 (0,240,1280,70) 不做全幅/色板量化/四联底测试条——工具清单见 `design/art/art-pipeline-tools.md`;**用户 2026-10-31 令只生成素材不做后续工序,本步须开工另发令**。
- [ ] 产物落盘:`artwork/`(在途,主区绝对路径)→ 验收后终稿 `game/assets/art/scene/`;替换先归档旧档;命名 `scene_camp_<键>.png` 对齐消费常量 `UiKit.TEX_CAMP_*`(asset-path-map.md §3/art-assets-checklist.md §3.5,先定资源路径常量、素材名跟它走,防断链)。
- [ ] 后处理(**授权形态已裁 2026-11-01:/iter 开工令即含本步授权**,不再两次发令):去底转透明(bg 不去底、全幅不透明)/擦通道水印/降采样/围墙层裁细条 (0,240,1280,70) 不做全幅/色板量化/四联底测试条——工具清单见 `design/art/art-pipeline-tools.md`。**降采样逐张登记换算式**(2026-11-01 双审补立):2560×1600 档=÷2 最近邻;**1024×1024 档无整数倍降采样(1024<1280)**——按非整数缩放显式特批登记(L2 记录+实现报告申报像素纪律局限),不回炉重出(追认 2026-10-31 已过审终稿优先、免美术线重开),禁现场默认。
- [ ] 产物落盘:`artwork/`(在途,主区绝对路径)→ 验收后终稿 `game/assets/art/scene/`;替换先归档旧档;命名 `scene_camp_<键>.png` 对齐消费常量 `UiKit.TEX_CAMP_*`(常量十键本批在 `ui_kit.gd` 新增——先定资源路径常量、素材名跟它走,防断链;asset-path-map.md §3/art-assets-checklist.md §3.5);texture 导入口径=mipmaps 关、filter 跟项目默认(Nearest,project.godot 现行)。
- [ ] 美术硬约束入产线(契约 7):48 色板/HSV 300°–345° 禁入带/语义色/WCAG/Nearest;**色板机检二器复用**(`palette_check.py` 48 令牌+新增色双门/`standee_asset_check.py` 像素 32 色冻结)。
- [ ] 方向稿选划留痕:用户 2026-10-31 已裁「第一张样本稿为终稿、保留全部待选稿、每个都还可以接受」,选划三段表进开工单;本批不再重跑方向稿工序。
- [ ] 终稿落盘「验收」口径=契约 9(迭代内验收:截图对比+shot_checker 机检+样张过审留痕)。
@@ -289,11 +297,13 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
### T2 场景视图与点选交互(≈1.5–2d)
- 取用:T1 十层终稿 `UiKit.TEX_CAMP_*`、`camp_scene_layout.tres` 坐标结构键、camp.md v2.2 点选反馈四态规格、§0.3 P3 设计稿(代码段开工门)。
- [ ] 在 `game/presentation/views/camp_scene_view.gd` 实现 `setup_layers()`:按 §0.4 z0–z6 铺 10 层(bg 不透明全幅/wall 透明细条 (0,240,1280,70)/门洞 x520–760 留透明缺口透出 bg 小路/八物件层);围墙不并回背景层、门洞不填死、门洞不被任何物件遮挡。
- 取用:T1 十层终稿 `UiKit.TEX_CAMP_*`、`camp_scene_layout.tres` 坐标结构键、camp.md v2.2 点选反馈四态规格、§0.3 P3 已销号(camp.md v2.2 四态规格即设计稿)。
- [ ] 节点选型(2026-11-01 godot-master 审查定案):**Control 根+TextureRect 层+子节点序即 z 序**;禁 CanvasLayer(绕开 LAYER_CONTRACT 树序落位)、禁 z_index、禁 Sprite2D 根(项目视图全 extends Control;全屏 UI canvas_items 拉伸需 Control 锚点;ui_registry.gd:76 落位口按子节点序 move_child)。命中区=TextureRect 分区+`_gui_input()`(§9-3 域内确认 TextureRect 分区)。
- [ ] 在 `game/presentation/views/camp_scene_view.gd` 实现 `setup_layers()`:按 §0.4 z0–z6 铺 10 层(bg 不透明全幅/wall 透明细条 (0,240,1280,70)/门洞 x520-760 留透明缺口透出 bg 小路/八物件层);围墙不并回背景层、门洞不填死、门洞不被任何物件遮挡。
- [ ] 实现七物件命中区 `bind_hotspots()` 与 `get_hotspot_rect()`:读 `camp_scene_layout.tres` 坐标键;七件两两不相交、不越画面、不侵入顶栏禁区 y≤48;篝火命中矩形只取下半(火堆),火焰只展示。
- [ ] 实现点选反馈四态 `set_feedback_state()`(零新素材、全程序化):悬停=轮廓亮边+浮动(整像素吸附)+名牌(下方 8px 居中、字号 14、对比 ≥4.5:1)/按下=位移 1px/禁用=降明度+去饱和(仅锻炉、宝箱)/已解锁=常态;提示文案与可点区域视觉按 §0.3 P3 设计稿销号后落(未销号=本段禁开工)。
- [ ] 场景屏顶栏预留位 `set_hud_band()`(高 48px 浮层覆盖、不挤压画面,卡⑤ 填值);刷新入口=本卡 T3 定义的营地刷新单口(卡⑤ 同口挂接)。
- [ ] `camp_scene_layout.tres` 加载期 fail-fast 校验(2026-11-01 双审补立):七命中区两两不相交/不越画面/不侵 y≤48,违则致命中止(fail-fast 红线;契约 7 几何红线机检化)。
- [ ] 实现点选反馈四态 `set_feedback_state()`(零新素材、全程序化):悬停=轮廓亮边+浮动(整像素吸附)+名牌(下方 8px 居中、字号 14、对比 ≥4.5:1)/按下=位移 1px/禁用=降明度+去饱和(仅锻炉、宝箱)/已解锁=常态。实现约束(2026-11-01 双审补立):悬停浮动位移经 Tween(禁 `_process` 逐帧移 UI);名牌 Label `mouse_filter = IGNORE`(防挡命中区,UiKit 惯例 ui_kit.gd:312/333);名牌位置加画面内钳制(贴底物件防出画);禁用态去饱和经 ShaderMaterial(modulate 只能调明度,去饱和需 shader——零新素材、代码件)。
- [ ] 场景屏顶栏预留位 `set_hud_band()`(高 48px 浮层覆盖、不挤压画面,卡⑤ 填值;载体循 UiKit `page_shell` 的 `PRESET_TOP_WIDE`+`custom_minimum_size` 先例 ui_kit.gd:523-525,禁手动 rect);刷新入口=本卡 T3 定义的营地刷新单口(卡⑤ 同口挂接)。
- [ ] 叠层规则显式派生并写入 V2 断言:随 §9 第 2 条承载形态裁定派生——独立全屏页=场景根隐藏(生涯先例)/单一内页切换=场景根留底;「缩放虚化」是两选项之外的第三形态(涉新演出面+素材),禁批内自选(若设计稿/用户想要,升格裁点)。
- [ ] 键盘焦点/手柄不涉(08:191-192 排 Beta;ESC 属卡⑤ 用户明示裁决面)。
- 产出:`CampSceneView.setup_layers()`/`bind_hotspots()`/`set_feedback_state()`/`get_hotspot_rect()`/`set_hud_band()`,`object_pressed(key: String)` 信号;T3 按接口块接线。
@@ -304,9 +314,9 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- [ ] 锻造工坊/秘宝匣/天赋树/图鉴四界面自 CampOverlay 抽出为独立可开界面(`forge_view.gd`/`treasure_view.gd`/`tree_view.gd`/`codex_view.gd`,承载形态=§9 拍板;约束=写口链路零改、零新素材、界面逻辑复用);**五界面各带返回/关闭钮**(现四者共用 CampOverlay 底栏关闭钮随拆解消失,卡⑤ Q1 甲案前提即此)。
- [ ] 按契约 11 拆分边界纪律落四界面:每界面写职责语句与单一变化原因(实现报告逐件一行)、有集体身份;禁凑计数袋、禁沿阈值轴硬拆;拆不出内聚单元=停卡呈报走豁免。
- [ ] 生涯页入口自主菜单生涯钮改挂帐篷旁笔记本(CareerOverlay 本体零改);**生涯页返回钮文案禁含「营地」字样**(ui_struct_probe.gd:382 nojump 禁词表含「营地」——career_overlay.gd:27 现「返回」安全,改文案时避坑)。
- [ ] 入口改挂后各界面开合走模态栈单口;层序入 `LAYER_CONTRACT`。
- [ ] **CampShopPresenter 承接面改造(契约 4)**:投影目标改线(五界面/场景根)、七写口尾刷新改路由(经营地刷新单口——**各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成**)、open/close 编排从 campbtn 迁位到卡③ 进档动作、CampFlowContext 字段语义订正、探针缝保位(main.gd:1163-1171);`on_camp_attune_toggle`(:98-106)尾只 refresh_camp、不调 `_refresh_presentations` 保持原样。
- [ ] 新建 `camp_refresh_bus.gd` 实现 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`(T0 接口块);卡⑤ HUD 刷新挂此单口,非已退役的 `refresh_camp`。
- [ ] 入口改挂后各界面开合走模态栈单口;层序入 `LAYER_CONTRACT`;五枚句柄(场景根+四子界面)落 `ui_camp_modals.gd` 子容器(卡⑥ 前置建件,寻址 `ui.modals.camp.X` 二层)——禁平铺回 `ui_modals.gd`(S13 上限,2026-11-01 用户裁)。
- [ ] **CampShopPresenter 承接面改造(契约 4)**:投影目标改线(五界面/场景根)、七写口尾刷新改路由(经营地刷新单口——**各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成**)、open/close 编排从 campbtn 迁位到卡③ 进档动作、CampFlowContext 字段语义订正、探针缝保位(组合根薄委托缝,T0 复核现役行号——原引 main.gd:1163-1171 超出文件实长);`on_camp_attune_toggle` 口尾(:102)只 refresh_camp、不调 `_refresh_presentations` 保持原样。
- [ ] 新建 `camp_refresh_bus.gd` 实现 `CampRefreshBus.refresh(camp_data: Dictionary) -> void`(T0 接口块);卡⑤ HUD 刷新挂此单口,非已退役的 `refresh_camp`;头注自陈「非 autoload、非信号广播——组合根单点推送」(防被读成已否决的全局事件总线形态,2026-11-01 双审注)。
- [ ] **图鉴占位态(契约 5)**:独立界面壳=§9 第 2 条承载形态;文案照录 camp_overlay_view.gd:168 现行锁文,「柜台结构位预留」中『柜台』称谓随形态订正(L2 记录,零新文案);V6 断言锚定新视图占位标签。
- [ ] **开层互斥与关层收闭**:营地开层时选档层被模态栈遮挡隐藏(卡③ T2 同口径);篝火/ESC 回主菜单=营地层与选档层同收+开始层恢复(现役 on_camp_closed 模式只视野开始层,选档层不在其视野——须显式)。
- [ ] CampOverlay 类退役处理(拆解后无消费面即删——吸收即死);`main.camp_overlay` 探针句柄迁移留痕。
@@ -318,9 +328,9 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- 取用:T2 的 `object_pressed(key: String)`、T3 的 `CampRefreshBus`、F6/F7 断点弹窗与冒险编排、Q5 已裁=甲三钮化、卡③ ConfirmDialog 契约 3。
- [ ] 大门点击→两分支确认弹窗(裁决 5):有断点=ConfirmDialog 三出口「继续上一段冒险/开始新冒险/取消」;无断点=「是否开始新冒险:是/否」;点大门永远先出确认、不直接进冒险。
- [ ] **三出口弹窗形态(Q5 已裁=甲)**:ConfirmDialog 三钮化+**消费方编排**(组合根单实例+待确认消费方状态量:open 前设、confirmed/cancelled 后分派;cancelled 恒=无变更退回营地;禁每次 connect/disconnect 漂移、禁双常驻静态接线——卡③ 契约 3 同款互指)。
- [ ] 断点弹窗自主菜单移位(main.gd:475-497 逻辑整体迁来,卡②已摘除原调用)。
- [ ] 断点弹窗移位:弹窗本体与两分支逻辑自组合根摘除位迁来(卡②已摘除原调用,main.gd:118-120 注释明载归本卡大门承接;main.gd:475-497 现为选档委托、非断点弹窗——旧引文失实,v3.1 订正)。
- [ ] 「继续上一段冒险」接现役续玩编排(`_resume_from_menu` 同源);「开始新冒险」接现役开局编排(`_start_adventure` 同源)——冒险屏/选路屏/战斗屏零改(F7)。
- [ ] 冒险发起后营地场景屏隐藏;**终局落点不变量**:现役口径=终局 continue 回主菜单(main.gd:703)——若后续改落点为营地,营地语境可能出现未收束会话,卡⑤「返回主菜单」动线须补 `_begin_session(false)`,两卡 T0 互证。
- [ ] 冒险发起后营地场景屏隐藏;**终局落点不变量**:现役口径=终局 continue 回主菜单(`main_adventure_flow.gd:123-143` 终局出口 `_begin_session(false)`——原引 main.gd:703 失实,v3.1 订正)——若后续改落点为营地,营地语境可能出现未收束会话,卡⑤「返回主菜单」动线须补 `_begin_session(false)`,两卡 T0 互证。
- [ ] 篝火回主菜单动线:命中区只含火堆(不含火焰),**必须二次确认**(camp.md v2.2 已裁,走 ConfirmDialog 先例);确认后营地层与选档层同收+开始层恢复(T3 开层互斥/关层收闭同口径)。
- [ ] `_refresh_resume_button` **不复用**(大门两分支直读 `has_breakpoint`,无钮可刷——函数随卡②删,防两卡互以为对方在处理)。
- 产出:大门两分支+三钮消费方编排+篝火二次确认动线;T5 按接口块验收。
@@ -332,15 +342,16 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- [ ] **无 camp golden**(契约 10):camp 验收=shot_probe 出图+shot_checker 语义断言段(文字 WCAG/明度三档带/文字禁压纹理/文字在判据扩 camp 取样面);棋盘/menu 既有 golden 复跑。
- [ ] F8 全量位点逐点迁移:
- [ ] 探针深员改指表:ui_probe `_balance_label`/`_tree_buy_btns`/`_workshop_rows`(:39-52)、ui_click_probe `_tree_buy_btns`/`_tree_fork_btns`+三处 `main.camp_overlay.refresh_camp(main._camp_data())` 直呼(:72/99/110)、ui_struct_probe audit 根(:45)——「改挂新入口」不够,这些是成员级耦合;七命中区与笔记本命中区的探针寻址名随 T2 命名稳定化一并定。
- [ ] smoke_all:6469-6475 源码断言段:三条锁四柜台等高形态本身——按新形态重写或注销(四栏等高纪律是否迁入新界面=§9 第 2 条裁后定)。
- [ ] theme_projection_test.gd:710-717 四柜台等高源码断言段(`_smoke_element_details`)+system_test.gd V2 实例化(:67-68)/V3g 模态开合(:520-541):四柜台等高三条锁按新形态重写或注销(四栏等高纪律是否迁入新界面=§9 第 2 条裁后定)——`game/smoke/smoke_all.gd` 已退役,v3.0 旧位点作废。
- [ ] ui_click_probe:500-528 断点确认段:改走大门路径断言(与 V3 合并或互指;卡② T2 明列移交、本卡 T5 接收)。
- [ ] ui_contrast_probe:48-55 营地令牌段:核签(新界面仍消费 `CAMP_HEADER_BAND`/`CAMP_ROW_PLATE`=段留;不再消费=段随注销)。
- [ ] forge_workbench_test.gd 单测:随锻造视图抽出同迁(断言零改、只换承载类);V4 复跑清单补该单测。
- [ ] 生涯段驱动改接:ui_struct_probe:54-69 与 shot_probe:103-124 现经 `_start_overlay`「生涯」钮驱动——改挂后须合成点击场景笔记本命中区;命中区若为 TextureRect/Area2D 而非 Button,`ProbeUiUtil.find_button` 失效,寻址面随 T2 扩展。
- [ ] screen_flow_probe 营地键新语义::53 改指新场景根句柄;子界面开态时键真值按 T2 叠层规则派生;:119-125 回退段改新场景根 closed 信号或篝火动线。
- [ ] 多卡共改文件分界:screen_flow_probe(入口路径段归卡②、键语义/开合/篝火段归本卡)、ui_click_probe(入口路径归卡②、槽操作路径归卡③、营地段+断点确认段归本卡)——每条探针注分界。
- [ ] screen_flow_probe 营地旗标新语义::54 改指新场景根句柄(:53 是菜单行,v3.1 订正);子界面开态时键真值按 T2 叠层规则派生;:119-125 回退段改新场景根 closed 信号或篝火动线。
- [ ] 多卡共改文件分界:screen_flow_probe(入口路径段归卡②、键语义/开合/篝火段归本卡)、ui_click_probe(入口路径归卡②、槽操作路径归卡③、营地段+断点确认段归本卡)——每条探针注分界;分界锚=断言段语义/段名,不锚行号(一侧插删行会平移另一侧行号,2026-11-01 双审补立)。
- [ ] 新增断言段(**正向验收断言,从真实入口进入、判据绑被测改动特有的可观测提交面**):七物件逐一可达对应界面;大门两分支;点大门不直接进冒险(绑 `confirmed` 信号与 `route_chosen` 未发);点大门→开局后营地场景屏隐藏;图鉴=占位挂起态;场景根态无文字按钮列表/无四柜台面板(反参照纪律断言);presenter 七写口尾→现开界面刷新+场景根 HUD 刷新一闸(绑刷新单口调用记录与 HUD 状态源取值);三钮弹窗 cancelled 恒=无变更退回营地(绑写口零调用+营地根 visible)。**判据绑不上=静态判据+评审注记申报观测面局限,证明做不出=停卡呈报;断言只在当轮真实跑批执行并通过,不做注入破坏。**
- [ ] 美术约束机检:shot_checker 现役段扩 camp 取样;截屏级 48 色计数是否新建=§9 第 5 条拍板(依赖 §0.3 环境子板色值代码段开工门销号)。
- [ ] 美术约束机检:shot_checker 现役段扩 camp 取样;截屏级 48 色计数是否新建=§9 第 4 条拍板(**条件步**:§0.3-3⑤环境子板色值已按 T1 量化登记→拍板;未登记→本子步显式跳过+T6 登记遗留,不阻塞批末闸)。
- [ ] **新增两条 headless 断言(2026-11-01 godot-master 审查补立)**:①`ui_struct_probe` camp 场景层序段——camp_scene_view 子节点序=layout 表 z 序逐层相等、各层纹理矩形与表逐值一致(本批核心视觉不变量的机器验收面);②`camp_scene_layout.tres` 加载期校验断言——载入数据件断言校验通过+七命中区几何(两两不相交/不越画面/不侵 y≤48),落 `game/tests/unit/presentation/camp_scene_layout_test.gd:_test_layout_hotspot_geometry`(新建单测件,随套件 manifest 登记)。
- 产出:探针/断言迁移留痕表(F8 全量位点逐点「改前→改后」)+新增断言段;T6 收口。
### T6 产出物落位卡(≈0.5d,末卡)
@@ -363,7 +374,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|
| V1 场景图形态(机检半) | `camp_scene_view.gd` 10 层铺底(z0 不透明全幅含门洞小路/z1 透明细条 (0,240,1280,70) 门洞缺口/z2–z6);`camp_screen.gd` 改挂;`shot_probe.gd` camp 段 | `shot_probe.gd:_ready`(camp 截图段)〔营地图批 V1〕+`shot_checker.py` 结构禁词段 | 探针 | 进入存档→选档→进档→营地场景屏(shot_probe 驱动) | camp 截图出图非空+shot_checker 结构禁词命中(无四柜台覆盖层/无文字按钮列表);「七要素可辨(构图)」归 V11 目检 | 新增:营地场景图十层铺底与门洞留空呈现 |
| V1 场景图形态(机检半) | `camp_scene_view.gd` 10 层铺底(z0 不透明全幅含门洞小路/z1 透明细条 (0,240,1280,70) 门洞缺口/z2–z6);`camp_screen.gd` 改挂;`shot_probe.gd` camp 段 | `shot_probe.gd:_ready`(camp 截图段)〔营地图批 V1〕+`ui_struct_probe.gd:_ready`(camp 场景层序段)〔营地图批 V1〕+`shot_checker.py` 结构禁词段 | 探针 | 进入存档→选档→进档→营地场景屏(shot_probe 驱动) | camp 截图出图非空+shot_checker 结构禁词命中(无四柜台覆盖层/无文字按钮列表);camp_scene_view 子节点序=layout 表 z 序逐层相等、各层纹理矩形与表逐值一致(2026-11-01 双审补立——核心视觉不变量机器验收面);「七要素可辨(构图)」归 V11 目检 | 新增:营地场景图十层铺底与门洞留空呈现 |
| V2 七物件可达性+叠层退避 | `camp_scene_view.gd` 七命中区+`object_pressed` 信号;`camp_scene_layout.tres` 坐标键;组合根开界面 | `ui_click_probe.gd:_ready`(七物件合成点击段)〔营地图批 V2〕 | 探针 | 命中区 `_gui_input()` → `object_pressed(key)` → 组合根开界面 | 七物件各自对应界面 visible 断言+场景根按 T2 叠层规则退避(树序/可见性);绑 `object_pressed` 信号发射面 | 既有:模态覆盖层编排与输入锁;新增:营地七物件命中区点选与分界面可达 |
| V3 大门两分支+开局收闭 | `main.gd:475-497` 断点弹窗移位;`confirm_dialog.gd` 三钮化;消费方编排 | `ui_click_probe.gd:_ready`(大门两分支段/断点确认段 :500-528)〔营地图批 V3〕 | 探针 | 大门命中区 → ConfirmDialog(三出口)→ `main._on_confirm_new_adventure()`/`_resume_from_menu()` | 有断点三出口/无断点两出口;点大门不直接进冒险(绑 `confirmed` 信号与 `route_chosen` 未发);cancelled 绑写口零调用+营地根 visible;开局后营地场景屏隐藏 | 既有:断点片段往返与续玩幂等;新增:营地大门两分支确认与断点续玩接线 |
| V4 五界面功能+写口链零回归 | 四界面 view 抽出;`camp_shop_presenter.gd` 承接面;`camp_flow_context.gd` 字段;`forge_workbench_test.gd` 迁移 | `forge_workbench_test.gd:_test_forge_rows`〔营地图批 V4〕+`ui_click_probe.gd:_ready`(写口链段)+`smoke_all.gd:_face_snapshot` 段 | 单元+探针 | 五界面意图信号(`buy_requested`/`tree_buy_requested`/`forge_*`)→ CampShopPresenter → MetaProgression 写口 | 树购买/Fork 切换/锻造三意图/秘宝购买/同调装配既有断言全链复跑绿;presenter 各写口尾 MetaProgression 原语调用序列不变(绑调用序列,不绑终态兜底) | 既有:元进度入账与锻造两轴;新增:四柜台拆解后五界面入口改挂与写口零回归 |
@@ -372,14 +383,15 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
| V7 篝火动线(二次确认) | `main.gd` 篝火回主菜单+开层互斥/关层收闭;`screen_flow_probe.gd` 段 | `screen_flow_probe.gd:_ready`(篝火/开合段)〔营地图批 V7〕 | 探针 | 篝火命中区(只下半火堆)→ ConfirmDialog 二次确认 → 主菜单 | 二次确认弹窗 `confirmed` 信号先于关层;确认后营地隐藏+选档层同收+主菜单可见(绑可见性/层序);命中区只含火堆不含火焰 | 新增:营地篝火回主菜单与笔记本改挂动线 |
| V8 顶栏预留位+刷新单口 | `camp_scene_view.gd` 顶栏 HUD 位;`camp_refresh_bus.gd` 新建;presenter 七写口尾改路由 | `ui_struct_probe.gd:_ready`(顶栏位段)〔营地图批 V8〕+`ui_click_probe.gd:_ready`(写口尾刷新段) | 探针 | 写口尾 → `CampRefreshBus.refresh(camp_data)` → 现开界面刷新+场景根 HUD 刷新 | 场景屏顶栏 HUD 位存在(结构断言,高 48px 浮层);各写口尾→现开界面刷新+场景根 HUD 刷新一闸完成(绑刷新单口调用记录与 HUD 状态源取值);卡⑤ 联验填值 | 新增:营地刷新单口与场景根 HUD 同步 |
| V9 反参照纪律 | `camp_scene_view.gd` 根态;`camp_overlay_view.gd` 退役;`smoke_all.gd:6469-6475` 源码断言段 | `ui_struct_probe.gd:_ready`(结构禁词段)〔营地图批 V9〕 | 探针 | 营地根态场景树审计 | 营地根态无文字按钮列表、无四柜台面板(结构禁词断言);四柜台等高源码断言按新形态重写或注销(迁移留痕) | 新增:营地图鉴占位挂起态与反参照结构纪律 |
| V10 美术约束 | `tools/shot_checker.py` camp 取样面;`ui_contrast_probe.gd` 令牌核签;素材色板二器 | `ui_contrast_probe.gd:_ready`(营地令牌段)〔营地图批 V10〕+`shot_checker.py` WCAG/明度三档带段 | 探针(机检) | camp 截图+素材文件(`palette_check.py`/`standee_asset_check.py`) | 文字 WCAG ≥4.5:1/明度三档带/文字禁压纹理底/文字在判据;素材色板(48 令牌+新增色双门/像素 32 色冻结);§9 第 5 条拍板截屏计数面 | 既有:UI 全局主题与按钮五态(含对比度锚);新增:营地场景图十层铺底与门洞留空呈现 |
| V10 美术约束 | `tools/shot_checker.py` camp 取样面;`ui_contrast_probe.gd` 令牌核签;素材色板二器 | `ui_contrast_probe.gd:_ready`(营地令牌段)〔营地图批 V10〕+`shot_checker.py` WCAG/明度三档带段 | 探针(机检) | camp 截图+素材文件(`palette_check.py`/`standee_asset_check.py`) | 文字 WCAG ≥4.5:1/明度三档带/文字禁压纹理底/文字在判据;素材色板(48 令牌+新增色双门/像素 32 色冻结);§9 第 4 条拍板截屏计数面(条件步,§0.3-3⑤) | 既有:UI 全局主题与按钮五态(含对比度锚);新增:营地场景图十层铺底与门洞留空呈现 |
| V11 目检项登记 | `reports/iter<N>-实现报告-<日期>.md` 实机目检总清单 | 件核对(07 清单登记,登记不阻塞)〔营地图批 V11〕 | 用户目检(非自动化) | 用户实机(生命周期末步统一进行,不阻塞迭代) | 场景观感/七物件辨识(纵深稿构图可辨)/点选反馈/大门两分支/篝火动线入 07 清单=登记即义务了结 | 新增:营地场景图十层铺底与门洞留空呈现 |
| V12 探针迁移留痕核验 | F8 全量位点逐点迁移 | 件核对表(T6 追认单)〔营地图批 V12〕 | 件核对(非自动化) | 探针脚本 diff 对账 | F8 全量位点逐点有「改前→改后」记录(契约 10 留痕义务) | 既有:模态覆盖层编排与输入锁;新增:营地七物件命中区点选与分界面可达 |
| V13 反哺点清单 | §5 每条有落点 | 件核对表〔营地图批 V13〕 | 件核对(非自动化) | §5 同步面逐条对账 | §5 每条有 `reports/iter<N>-反哺点-<日期>.md` 落点 | 既有:模态覆盖层编排与输入锁 |
| V14 layout 数据件校验(2026-11-01 新增) | `camp_scene_layout.tres` 加载期 fail-fast 校验(schema 类+表现域容器类型化成员,先例 battle_layout.tres) | `camp_scene_layout_test.gd:_test_layout_hotspot_geometry`(新建单测件 `game/tests/unit/presentation/`,随套件 manifest 登记)〔营地图批 V14〕 | 单元 | 单元测试载入 `camp_scene_layout.tres` | 校验函数通过+七命中区两两不相交/不越画面/不侵 y≤48(契约 7 几何红线机检化) | 既有:模态覆盖层编排与输入锁;新增:营地场景图十层铺底与门洞留空呈现 |
| B1 组合根接线总改造(行为改动专行) | `main.gd`(断点弹窗移位+三钮消费方编排+营地入口改挂+篝火动线+开层互斥);`confirm_dialog.gd` 三钮;`camp_shop_presenter.gd` 写口尾路由;`camp_flow_context.gd` 字段;`camp_refresh_bus.gd` 单口 | `ui_click_probe.gd:_ready`(断点确认段)+`screen_flow_probe.gd:_ready`(篝火段)〔营地图批 B1〕 | 集成 | `_on_confirm_new_adventure()`/`_resume_from_menu()`/`_start_adventure()`/`CampRefreshBus.refresh()` | 三钮消费方编排:open 前设消费方、confirmed/cancelled 后分派;cancelled 绑写口零调用;写口尾绑 `CampRefreshBus.refresh()` 调用记录+现开界面+场景根 HUD 同刷;`on_camp_attune_toggle` 尾只 refresh_camp 保持原样 | 既有:断点片段往返与续玩幂等;新增:营地刷新单口与场景根 HUD 同步 |
| B2 CampOverlay 退役+五界面抽出(行为改动专行) | `camp_overlay_view.gd` 退役;四界面 view 抽出;`career_overlay.gd` 入口改挂;`smoke_all.gd:6469-6475` 源码断言段 | `smoke_all.gd:_ready`(源码断言段重写)+`forge_workbench_test.gd:_test_forge_rows`(承载类换)〔营地图批 B2〕 | 集成 | 五界面 `open()`/`close()` → 模态栈单口 | CampOverlay 无消费面即删(吸收即死,绑 `main.camp_overlay` 句柄迁移留痕);四界面各带返回/关闭钮;拆分沿职责轴(契约 11:职责语句/单一变化原因/集体身份,禁凑计数袋) | 既有:元进度入账与锻造两轴;新增:四柜台拆解后五界面入口改挂与写口零回归 |
**落点集中**:V1/V9/V11/V12 → T5/T6;V2/V8 → T2/T3;V3/V7 → T4;V4/V5/V6 → T3;V10 → T5;V13 → T6;B1 → T3/T4;B2 → T3/T5。全表名实核对归 T6。
**落点集中**:V1/V9/V11/V12 → T5/T6;V2/V8 → T2/T3;V3/V7 → T4;V4/V5/V6 → T3;V10 → T5;V13 → T6;V14 → T2/T5;B1 → T3/T4;B2 → T3/T5。全表名实核对归 T6。
**测试方式两分法**:V1–V10、B1–B2 机检自动化(能力验证经调试接口直改断点/钥石/解锁状态,沿用 DebugOverlay/DS_DEBUG 门控,不走正常资源积累流程);V11 归用户目检(生命周期末步统一进行,不阻塞迭代);V12/V13 为件核对表(非自动化,T6 追认单)。**用户目检一律「登记不阻塞」**:登记完成即卡内义务了结,验收时间由用户自行安排,不得作为批末收尾、合并或下一迭代开工的前置条件。
@@ -409,7 +421,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- **L1(自救一次)**:断言写法、探针路径、命中区微调、命名、编译错误、签名拼写、测试套件 manifest 登记遗漏。
- **L2(记录继续;同一红连续三次升 L3)**:场景屏承载形态选择(§9 域内)、点选反馈参数(§0.3 P3 设计稿域内)、四界面承载形态(§9 域内)、图鉴占位文案称谓订正、行数较 T0 预期漂移但 S13 键不升。
- **L3(立即停卡写报告等用户)**:
- 代码段开工门未销号即需开工对应任务段(§0.3:P3 点选反馈设计稿/P5 GDD·art-bible 落文/camp-scene-prompts §5 五裁点/素材后处理入库);
- §0.3 代码段开工门裁断实况反转(四项 2026-11-01 已裁断销号;开工时发现未销号实况=停机呈报);
- 规格清单(样张批)终档置换未按反哺点登记;
- golden 漂移越出预期面(若涉及既有棋盘/menu 基线);
- 发现五界面任一功能/写口链路被意外触碰(契约 4 破);
@@ -427,7 +439,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
1. 场景屏承载形态(camp_screen 屏壳改造 vs 新屏壳)——约束:`LAYER_CONTRACT` 更新+探针挂载面迁移留痕。
2. 五界面拆出后的承载形态(独立全屏页 vs 单一内页切换)+**叠层规则派生**(独立全屏页=场景根隐藏/单一内页切换=场景根留底;写入 V2 断言;「缩放虚化」禁自选)。
3. 七物件命中区实现(TextureRect 分区 vs 其他)与点选反馈参数(§0.3 P3 设计稿域内)。
4. 截屏级 48 色计数工具是否新建及落点(现役=素材级二器 F17;截屏级依赖 §0.3 环境子板色值代码段开工门销号后定)。
4. 截屏级 48 色计数工具是否新建及落点(现役=素材级二器 F17;**条件步**——§0.3-3⑤环境子板色值已按 T1 量化登记则拍板,未登记则本子步显式跳过+T6 登记遗留)。
5. CampOverlay 类退役范围(整类删 vs 留片段)——约束:吸收即死,禁留死代码。
6. 提交切分与 worktree 分支命名。
@@ -441,7 +453,7 @@ req-card 第〇步+卡头 2026-10-21 退回令口径=美术设计闭合(
- **Q2 参照物=已补**(2026-10-23):`artwork/2026-10-23-营地场景/Gemini_Generated_Image_ykrc0nykrc0nykrc.jpg`,已进 camp-scene-prompts.md §1 登记为「作者提供的参照物」,与纵深稿对过表、围墙结构冲突已呈报;参照物只是参考图,保留 `artwork/`、不入 git、不入游戏目录。
- **Q4 营地底栏「最近冒险」条带去留=甲**(2026-10-18):随四柜台拆解一并撤下,逐局历史回看整体归生涯(期二承接);生涯.md P6 同批注销(反哺点)。
- **Q5 大门「有断点」分支三出口弹窗=甲**(2026-10-18):ConfirmDialog 扩第三钮(继续上一段冒险/开始新冒险/取消,cancelled 恒=无变更退回营地);卡③ 契约 3 已按三钮化落定(删档确认只用取消/确认)。
- **Q6 Q1 与规格清单缺口连带裁=甲**(2026-10-18):过渡尺寸档=现役 1280 工程档(1280×800)+烛光黄 #F6C667/木色 #8A5A34;渠道 B 已裁(2026-10-22),规格落 art-bible v3.24 §6.2/art-assets-checklist.md §3.5(2× 源档出图、最近邻降采样到 1280×800);终档归样张批冻结清单后置换(反哺点登记),执行归用户线。
- **Q6 Q1 与规格清单缺口连带裁=甲**(2026-10-18):过渡尺寸档=现役 1280 工程档(1280×800)+烛光黄 #F6C667/木色 #8A5A34;渠道 B 已裁(2026-10-22),规格落 art-bible v3.31 §6.2/art-assets-checklist.md §3.5(2× 源档出图、最近邻降采样到 1280×800);终档归样张批冻结清单后置换(反哺点登记),执行归用户线。
- **Q8 设计稿产出方式=甲**(2026-10-18):批内出稿呈裁(方向稿先行纪律照旧——方向稿选划已由 2026-10-31 样张过审裁定完成,各素材第一张样本稿为终稿、保留全部待选稿)。
**代码段 `/iter` 开工门(不是设计待裁点,不阻塞重立卡与编迭代)**,见 §0.3:P3 点选反馈设计稿、P5 GDD/art-bible 条款落文、camp-scene-prompts.md §5 五个待裁点(帐篷尺寸/门扇高宽/布告板位置/描边颜色追认/环境子板色值)、十张终稿后处理与落 `game/assets/art/scene/`(实现段 T1/`asset-ingest` 第 3 步,用户 2026-10-31 令只生成素材不做后续,开工时另发令)。编入哪一迭代归 iter-plan 段发令,本卡不代裁。
**代码段 `/iter` 开工门已全部裁断销号(2026-11-01 用户裁,照 grilling+godot-master 双审推荐采纳)**:P3 销号口径=camp.md v2.2 四态规格即设计稿;P5 销号口径=形态落文完成、实装回填归反哺点;camp-scene-prompts §5 五裁点=①帐篷 300×170 追认②门扇 240×220 追认③布告板 (845,268,130,140)④描边奶油色追认⑤环境子板色值不前置(T1 量化后按 art-bible §4.1 登记);十张终稿后处理=/iter 开工令即含授权。**本卡零待裁点、开工门清零——迭代 47 可开工。**
@@ -0,0 +1,144 @@
# 需求卡片:营地模态句柄子容器批(UiCampModals 分域句柄容器 · camp_overlay 句柄迁移)· 营地交互重做卡⑥(前置基建)
- 版本:**v1.1(现行有效卡)**。留痕:2026-11-01 用户裁(迭代 47 机检预算互斥停机呈报裁决=最小改动先走:营地域六句柄落子容器,全量分域分家登记待办清单另立批);2026-11-01 grilling+godot-master 双审后写卡段修订(v1.1,执行级四缺口+措辞订正,设计面零改动)——T0 命令路径订正 game/tests/static_scan.sh、T1 补类名缓存刷新步+ui_modals 头注同步+断言尾缀落点+复跑面扩改指面 8 文件、V3 判据改计数留痕+观测面局限申报、迁移面实测 8 文件入 F4、契约 2/4 计数与退役删除句、node() 改写分支措辞。
- 来源:2026-11-01 用户裁决(**唯一事实源**:本卡 §2 现状实测+`design/engineering/code-conventions.md` §1.1 拆分判据;需求性工程条目证据指认=迭代 47 机检预算互斥停机呈报〔ui_modals.gd 类变量 14,卡④⑤ 平铺合计 19>S13 上限 15〕+用户裁决留痕,属重构基建、无设计线前置)
- 归属:呈现层/组合根(`game/presentation/`)——营地交互重做卡⑥,**迭代 47 执行序第一位**(卡④ T3 落五句柄、卡⑤ T1 落浮窗句柄的本批前提)
- 开工门:U0(随迭代 47 `/iter` 发令,无独立开工令)+零待裁点
## 1. 目标与完成定义
**目标**:给营地域模态句柄立独立子容器,使迭代 47 营地域六句柄(场景根+四子界面+Esc 浮窗)不再平铺进 `ui_modals.gd`——该文件类变量终值 14(不超 S13 上限 15),零基线登记;调度器/层序合同/登记口三件全局职责零改动。
**完成定义**:
1. `ui_camp_modals.gd`(类名 UiCampModals,RefCounted)新件在役;`ui_modals.gd` 持 `camp` 成员一枚,`camp_overlay` 平铺成员删除——类变量 14→14;
2. `camp_overlay` 句柄寻址迁移 `ui.modals.camp.overlay`,全仓引用(组合根+探针)改指完毕,现役营地探针段复跑绿(行为零变化的证明);
3. `ui_registry.gd node()` 的 `camp_overlay` 分支返回属性链改经子容器(`modals.camp.overlay`;**LAYER_CONTRACT key 平铺不改名**,camp.* 新增解析分支随卡④⑤);
4. `main_ui_build.gd` 装配赋值改经子容器;S13 读数 `ui_modals.gd`=14 不升;
5. 批末按改动域选档全绿;三份 `reports/` 产出物写齐(T2)。
## 2. 现状事实(2026-11-01 实测;开工日 T0 复核)
| # | 事实 | 出处 |
|---|---|---|
| F1 | `ui_modals.gd` 类变量 14 枚=flow+13 模态句柄;gdstyle 直跑零命中(14≤15) | `game/presentation/ui_modals.gd:7-20` |
| F2 | S13 类变量上限=15(gdstyle v0.4.0 默认 `quality/max-class-variables`,超即 NEW 键红);15 枚文件直跑零命中、20 枚合成文件实测 warn | `tools/gdstyle_gate.py`;停机呈报实测凭据 |
| F3 | 卡④⑤ 原平铺写法合计 19>15(卡④ 净+4=场景根+四子界面−`camp_overlay` 退役;卡⑤ +1 浮窗)——迭代 47 机检预算互斥成因;2026-11-01 用户裁=子容器方案,禁基线特批 | 停机呈报-迭代47机检预算互斥-2026-11-01.md(iter47 worktree 在档,裁决已吸收本卡与卡④⑤) |
| F4 | `camp_overlay` 句柄现役消费面实测 **8 文件 40 余处**(2026-11-01 双审实查;T0 逐处列清单):`main_ui_build.gd`(约 10 处)/`ui_registry.gd:61`/`screen_flow_probe.gd`(3)/`ui_click_probe.gd`(16–20)/`ui_probe.gd`(8–9)/`shot_probe.gd`(2)/`ui_struct_probe.gd`(1)/`system_test.gd`(2——:108 句柄同一性链、:121 候选层清单,含 `game/tests/` 域) | `main.ui.modals.camp_overlay` 全仓引用位 |
| F5 | `ui_registry.gd node()` 现经 `modals.X` 一层寻址;LAYER_CONTRACT 与 `apply_layer_contract` 与本批正交(句柄册结构≠场景树结构——子容器不挂树、零树序影响) | `game/presentation/ui_registry.gd`(node() 寻址段,T0 复核) |
## 3. 设计契约(红线,违反=L3 停机呈报)
🔴 **契约 1 · 行为零变化**:纯句柄搬家+容器新立;LAYER_CONTRACT/登记序/树序/`register_layer`/ModalFlow 零改动;现有探针断言**零改判据**(只改寻址)。
🔴 **契约 2 · 寻址唯一形态**:营地域全部句柄(过渡期含本卡迁入的 `camp.overlay`;**终态六枚=场景根+四子界面+Esc 浮窗**)寻址一律 `ui.modals.camp.X` 二层;禁平铺回 `ui_modals.gd`(卡④ T3 落五句柄、卡⑤ T1 落 esc 时同规——卡⑤ 契约 7 已载、卡④ T3 步与文件表已载)。
🔴 **契约 3 · UiModals 只减不增**:本卡后类变量 14(flow+12 平铺句柄+camp 容器成员),卡④⑤ 阶段终值保持 14;任何新句柄平铺回顶层=S13 NEW 红=L3。S13 红线实际兜 **+2 起**(14→15 闸零命中)——+1 漂移由 V3 计数留痕与独立验收审计兜,契约分工在此写明。
🔴 **契约 4 · 禁无效成员**:子容器内不预占句柄——本卡只迁 `camp_overlay` 一枚实件;五枚新句柄随卡④ T3 创建视图时落、esc 随卡⑤ T1 落;禁空占位凑结构(code-conventions §1.1 第⑤条同款)。**`camp.overlay` 枚随卡④ T3 CampOverlay 类退役一并删除**(禁悬挂——类删后类型悬挂即 parse error,删除义务显式归卡④ T3)。
🔴 **契约 5 · 拆分边界纪律**(判据权威=code-conventions §1.1,本卡写条款不复制定文):本卡新建分域容器沿**职责轴**——UiCampModals=可命名领域概念「营地交互域模态组」(营地场景图+其子界面+其浮窗)、单一变化原因=营地交互重做;非凑计数袋;实现报告逐件一行承载职责语句。
## 4. 范围边界
**做**:UiCampModals 新件+`ui_modals.gd` 持 camp 成员+`camp_overlay` 句柄迁移+`node()` camp 二层寻址+装配与全仓引用改指。
**不做**(明示去向):
- 其余 12 枚存量句柄分域分家、flow 自 UiModals 升格独立持有 → 待办清单「模态句柄册分域重构」条(2026-11-01 登记,另立批);
- 卡④ 的五枚新句柄实装 → 卡④ T3;esc 句柄 → 卡⑤ T1;
- LAYER_CONTRACT/模态栈语义任何改动 → 无(契约 1)。
## 5. 关联文档同步面
| 文档 | 关联点 | 同步义务 |
|---|---|---|
| `design/engineering/architecture/architecture.md` | 呈现层模块清单(UiCampModals 新件归属) | 反哺点 · 列清单(实现批禁写 `design/`) |
| `待办清单.md`(仓库根) | 营地交互重做条实况补卡⑥路径;本卡来源裁决 | 已同步(2026-11-01 写卡段登记维护)——实现批禁写 |
| `roadmap.md`(仓库根) | 迭代 47 行需求卡片列(卡⑥ 排第一)+预计周期 | 已换代(2026-11-01 编排入表,写卡/编排段动作)——实现批禁写 |
| `game/tests/README.md` | 能力行:**零新增能力**(V1–V3 全落既有「模态覆盖层编排与输入锁」) | 已核:零增行;实现批禁写 |
| `game/presentation/*`、`game/probe/*`、`game/tests/**` | 实施/断言本体 | 实施本体(可写) |
## 卡前·文件结构与职责(T0 前)
| 文件 | 职责 | 改动类型 | 落位 | 对照表行 |
|---|---|---|---|---|
| `game/presentation/ui_camp_modals.gd` | 营地域模态句柄子容器(RefCounted;本卡 1 枚 `overlay`,卡④⑤ 落满 6 枚;头注自陈禁平铺回 ui_modals) | 行为改动 | 新建 | V1 |
| `game/presentation/ui_modals.gd` | 持 `camp` 成员(_init 构造);删除平铺 `camp_overlay` 成员 | 行为改动 | 复用既有(改) | V1 |
| `game/presentation/main_ui_build.gd` | 装配赋值改经子容器(`modals.camp.overlay = …`) | 行为改动 | 复用既有(改) | V1 |
| `game/presentation/ui_registry.gd` | `node()` 的 `camp_overlay` 分支返回属性链改经子容器(key 平铺不改名);camp.* 新增解析分支随卡④⑤ | 行为改动 | 复用既有(改) | V2 |
| 全仓 `camp_overlay` 句柄引用位(组合根+探针,T0 grep 列清单) | 寻址改指 `ui.modals.camp.overlay`(断言零改) | 非行为改动(寻址改指) | 复用既有(改) | V1 |
## 落位与可复用性预检
| 新件 | 复用面(grep 同职责实现) | 唯一面(grep 同名同义标识符) | 分层与交互面 |
|---|---|---|---|
| `ui_camp_modals.gd` | grep 句柄分域容器/分组容器无先例;`UiModals` 本体=按技术角色平铺的注册面容器(职责不同);`ModalFlow`=调度器非句柄册 → 不能复用,须新建 | grep `UiCampModals`/`ui_camp_modals`/`camp_modals` 无第二处;营地句柄寻址唯一落点 | RefCounted 不挂树;`ui_modals._init` 构造 camp(与 flow 同拍,先例 ui_modals.gd:24)、`main_ui_build` 只向 camp 赋句柄;`register_layer`/LAYER_CONTRACT/树序零触碰 |
**非新增件说明**:`ui_modals.gd`/`main_ui_build.gd`/`ui_registry.gd`/探针=存量改造;**数据件/RNG/Core 零触碰**(纯呈现层簿记);Core 零 UI 依赖不因新件绕过。
## 实现规范预算
gdstyle v0.4.0 默认阈值:公开方法 ≤20、类变量 ≤15、单函数分支 ≤8、返回 ≤6。
| 触面 | ①公开方法数 | ②成员变量数 | ③迁移函数对账 | 结论 |
|---|---|---|---|---|
| `ui_camp_modals.gd` | 预期 ≤1(`_init`) | 预期 1(本卡)→6(卡④⑤ 落满) | 无迁入函数 | 三查通过 |
| `ui_modals.gd`(存量) | 不适用 | 14→14(−平铺 `camp_overlay`+`camp` 容器成员) | 无 | S13 只降不升 |
| `ui_registry.gd`(存量) | 不适用 | 不适用 | 无 | node() +1 解析分支,分支数 ≤8 |
| `main_ui_build.gd`(存量) | 不适用 | 不适用 | 无 | 装配行改指 |
## 6. 任务卡
### T0 事实坐实(≈0.25d)
- 取用:§2 F1–F5、营地交互重做条(已编迭代 47)、卡⑤ v1.5 契约 7+卡④ v3.2 T3 步与文件表(寻址规则落点)。
- [ ] 复核 §2 全部 `file:line`;grep 全仓 `modals.camp_overlay` 引用位列清单(改指面:组合根+探针逐处)。
- [ ] 实读 `ui_registry.gd node()` 寻址形态,定 `camp.*` 二层解析的顺延写法(循 `modals.X` 先例,禁另造第三种寻址)。
- [ ] 跑 `bash game/tests/static_scan.sh`(tools/ 下无此件——v1.0 路径失实订正)记录 S13 读数(**禁手改 `tools/gdstyle_baseline.txt`**——基线是欠账簿,重生成人工专用;`ui_modals.gd` 类变量 14)。
- 产出:引用位清单、寻址形态结论、S13 读数留痕。
### T1 实装与验证(≈0.25d)
- [ ] 新建 `ui_camp_modals.gd`(RefCounted;`var overlay: CampOverlay`;头注自陈「营地交互域句柄子容器,终态六枚,新营地句柄禁平铺回 ui_modals」)。
- [ ] **类名缓存刷新**(code-conventions 语言纪律 5——新 class_name 必步):新件落盘后、任何 headless 验证前先跑一次编辑器扫描(`--editor --quit`;新 worktree 无 `.godot` 缓存,缺此步 `--script` 模式 parse 假红)。
- [ ] `ui_modals.gd`:持 `camp: UiCampModals`(`_init` 构造,与 flow 同拍——ui_modals.gd:24 先例);删除平铺 `camp_overlay` 成员;**头注同步**(「十三个模态覆盖层句柄」→「十二个平铺句柄+营地域子容器」——§1.4.1 头注如实义务)。
- [ ] `main_ui_build.gd` 装配改指;全仓引用位按 T0 清单(8 文件)改指 `ui.modals.camp.overlay`(逐处留痕)。
- [ ] `ui_registry.gd` `node()` 的 `camp_overlay` 分支返回属性链改经子容器(**key 平铺不改名**);camp.* 新增解析分支随卡④⑤。
- [ ] 现役营地断言消息文本追加〔句柄子容器批 V1/V2〕尾缀(**判据零改、仅消息文本**——独立验收审计「尾缀能 grep 到」义务的落点步)。
- [ ] 验证:改指面 8 文件逐件复跑绿(探针五件营地段+`system_test.gd`——screen_flow_probe/ui_click_probe/ui_probe/shot_probe/ui_struct_probe;断言零改只换寻址);`system_test.gd` V1c 合同树序链(:94-106)+句柄同一性链(:108)复跑绿;S13 读数=gate 绿(≤15 零 NEW)+`grep -c '^var '` 计数=14 留痕。
- 产出:子容器在位+迁移绿证+引用位改指留痕表;卡④ T3/卡⑤ T1 按契约 2 落新句柄。
### T2 产出物落位卡(≈0.25d,末卡)
- [ ] 里程碑步出口跑一次改动域全套闸:`bash tools/run_all_gates.sh --code`;回执 `all_green=true`;波内修复只做出红最小单元最窄确认。
- [ ] 独立验收审计(未参与实现的会话/子代理),产出 `reports/iter<N>-验收审计-<日期>.md`;未过禁合并。
- [ ] 合并前写齐三份 `reports/`:实现报告/反哺点清单(§5 每条有落点)/验收审计。
- 产出:三份 `reports/` 齐全+闸全绿+审计通过;合回后本地 CI 全量复验。
**估时合计 ≈0.75d**(T0 0.25+T1 0.25+T2 0.25)。**切分预案**:T2 偏紧(闸+审计+三报告挤 0.25d)——三份 `reports/` 先行成稿、独立验收审计走并行子代理,不串行等。
## 7. 验证闸:改动—验收对照表
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|
| V1 句柄迁移零行为漂移 | `ui_camp_modals.gd` 新件;`ui_modals.gd` 持 camp/删平铺成员;`main_ui_build.gd` 装配改指;全仓引用改指 | 改指面探针五件营地段+`system_test.gd`(V1c :94-106+句柄同一性链 :108)〔句柄子容器批 V1〕 | 探针(+系统测试复跑) | 现役营地断言逐件复跑(断言零改、寻址新——改指面 8 文件全覆盖) | 全段绿=行为零变化证明;改指后全仓 grep 零 `modals.camp_overlay` 平铺寻址残留(静态判据);句柄同一性链绿=同一实例零漂移 | 既有:模态覆盖层编排与输入锁 |
| V2 层序合同零漂移 | `ui_registry.gd` node() 分支返回属性链改写 | `system_test.gd:_ready`(V1c 合同树序链 :94-106 复跑)〔句柄子容器批 V2〕 | 系统测试 | LAYER_CONTRACT 单一事实源 | 树序链绿=合同与落位零漂移(子容器不挂树、零树序影响;key 平铺不改名) | 既有:模态覆盖层编排与输入锁 |
| V3 S13 读数 | `ui_modals.gd` 类变量 14 | gdstyle S13 gate+`grep -c '^var '` 计数(T1 验证步留痕)〔句柄子容器批 V3〕 | 静态读数 | `bash game/tests/static_scan.sh` | gate 绿(≤15 零 NEW)+计数=14 留痕——**观测面局限申报**:S13 闸对 14/15 同样零命中,「=14」由人工计数承载(test-conventions §8.4 静态判据出口) | 既有:模态覆盖层编排与输入锁 |
**测试方式两分法**:V1/V2 机检自动化;V3 静态读数留痕(gdstyle 基线机制域内)。**测试削弱申报:零削弱**(断言零改判据,只换寻址)。**独立验收审计**:合并前由未参与实现的会话/子代理执行(四件事口径同 test-conventions §8.7),报告落 `reports/iter<N>-验收审计-<日期>.md`,未过禁合并。**闸口径**:轻闸每卡出口自跑;批末 `--code` 关联档全绿才合并;MC 零跑(零数值零行为)。回归措辞按模板:里程碑步出口一次全套闸,波内修复只跑红件。
## 8. 异常分级
- **L1(自救一次)**:改指拼写、探针寻址写法、node() 分支写法、类名缓存失配 parse 失败(重跑 `--editor --quit` 即清)、manifest 登记遗漏。
- **L2(记录继续;同一红连续三次升 L3)**:行数较 T0 预期漂移但 S13 键不升。
- **L3(立即停卡写报告等用户)**:迁移后任一现役营地断言红且非寻址成因(=行为被意外触碰);机检预算互斥(`ui_modals.gd` 类变量被迫>14、或子容器被塞入非营地句柄=拆分边界破)——停卡退回写卡段,禁造第三条路。
## 9. 代拍板白名单(超清单必呈报)
1. `node()` 解析形态=match 平铺(现役唯一形态,ui_registry.gd:50-69;字典形态弃用、禁另造第三种寻址)。
2. 子容器头注措辞(契约 4 自陈内容域内)。
超出必须呈报:寻址形态、文件落位、契约数字、句柄归属。
## 10. 待裁点:零待裁点,开工门=U0(随迭代 47 发令)
**零待裁点。** 2026-11-01 用户裁留痕:最小改动=本卡+卡④ v3.2/卡⑤ v1.5 落子容器;全量分域句柄分家=待办清单「模态句柄册分域重构」条(另立批,不属迭代 47)。
+2 -2
View File
@@ -6,7 +6,7 @@
- **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。
- **迭代位**:迭代 1–45 全部已完成——迭代 45(呈现层状态件+gdstyle 尾波补件两卡)2026-10-31 合回(合并提交 f817ab2d,独立验收审计通过零缺漏,行为零变化),批后收尾闭合(2026-10-31):反哺点七条处置闭合(architecture 六条落文=breakpoint_store/board_hover/单格两件/站桩两件四行现状化+构造句改述与 7 项引用束/15 贴顶实况+gdstyle 截断缺陷节清偿结果;第 7 条工具观察迁待办新条)、目检三项登记体验验证清单 ㊸、产出物退役九件(两报告/反哺点/审计/两停机呈报+两卡+两设计文档)、worktree 两件清理(iter45+assert-slim-b2)。迭代 44 收尾记录见 git 提交 9eedf896。
- **下一步**:迭代 46 进行中〔用户裁拆批 2026-10-31〕——卡A 换图系双基座七件批+卡B 站桩名字行提档与地台色合并批已收口合回(合并提交 34bb62a5,独立验收审计通过,CI 全量绿);卡C 棋盘格边长口径收口批 **v2.0 重立回编**(用户裁锚点换代:可读性锚点=用户验收的现行渲染值,基座档 14×14=28px 达标,battle.md 可读下限 30px 句与探针 36px 人造阈值全退役,探针判据换锚=实读与单点算式严格相等+结构钳制+13/14 推定留痕;v2.0 卡经 grilling 独立复核,写卡批合并提交 e7090bc9,CI 全量绿),待 `/iter` 发令回 worktree iter46 续工(WIP 算式单点与四档冒烟两件复用、探针件返工)。battle.md `:144` 可读下限句退役归文档批待发令。迭代 47(营地交互卡④⑤)已编排待开工。待编池:冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)。
- **下一步**:迭代 46 进行中〔用户裁拆批 2026-10-31〕——卡A 换图系双基座七件批+卡B 站桩名字行提档与地台色合并批已收口合回(合并提交 34bb62a5,独立验收审计通过,CI 全量绿);卡C 棋盘格边长口径收口批 **v2.0 重立回编**(用户裁锚点换代:可读性锚点=用户验收的现行渲染值,基座档 14×14=28px 达标,battle.md 可读下限 30px 句与探针 36px 人造阈值全退役,探针判据换锚=实读与单点算式严格相等+结构钳制+13/14 推定留痕;v2.0 卡经 grilling 独立复核,写卡批合并提交 e7090bc9,CI 全量绿),待 `/iter` 发令回 worktree iter46 续工(WIP 算式单点与四档冒烟两件复用、探针件返工)。battle.md `:144` 可读下限句退役归文档批待发令。迭代 47(营地交互卡⑥句柄子容器前置+卡④⑤)已编排待开工。待编池:冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)。
## 里程碑时间线
@@ -80,7 +80,7 @@
| 迭代 44 | 已完成(2026-10-30) | 已退役(git 历史可考) | 2026-10-29 | ≈7.875 个工作日 | 已退役(git 历史可考) |
| 迭代 45 | 已完成(2026-10-31) | 已退役(git 历史可考) | 2026-10-29 | ≈7 个工作日 | 已退役(git 历史可考) |
| 迭代 46 | 进行中 | reports/需求卡片-换图系双基座七件批-2026-10-31.md<br>reports/需求卡片-站桩名字行提档与地台色合并批-2026-10-31.md<br>reports/需求卡片-棋盘格边长口径收口批-2026-10-30.md | 2026-11-01 | ≈8.625 个工作日 | — |
| 迭代 47 | 待开工 | reports/需求卡片-营地场景图新建批-2026-10-18.md<br>reports/需求卡片-ESC浮窗与营地顶栏批-2026-10-18.md | 2026-11-01 | ≈14.25 个工作日〔用户特批 2026-11-01 超 10 个工作日〕 | — |
| 迭代 47 | 待开工 | reports/需求卡片-营地模态句柄子容器批-2026-11-01.md<br>reports/需求卡片-营地场景图新建批-2026-10-18.md<br>reports/需求卡片-ESC浮窗与营地顶栏批-2026-10-18.md | 2026-11-01 | ≈15 个工作日〔用户特批 2026-11-01 超 10 个工作日〕 | — |
- 迭代 38 收口为带红合并〔用户特批 2026-10-26〕:09 静态扫描 S13 gdstyle 恶化 2 条经批准记入基线存量红(git 提交 c4c69701),卡② 停卡退上游,处方已于 2026-10-26 修订定稿 v1.3(git 提交 b3ea7532),随「gdstyle 存量债全量清偿」条目波 7 待编排。
- 迭代 39 收口为带红合并〔用户特批 2026-10-27〕:02 EV 锚 goblin 0.9148∉[1.00,1.20](触发器批有意行为改动,fp f74b9a48ef4a)已随 EV 锚重锚批换带归绿(git 提交 ba5105aa);08d 生涯.md:55 行号漂移已随 2026-10-28 批后收尾反哺落文修复归绿。三卡拆分后终局 gdstyle 恶化 2 条(battle_orchestrator max-file-length、level_map_screen max-class-variables)归迭代 40 卡①还债与迭代 41 存量债序列。
+12 -3
View File
@@ -448,11 +448,20 @@
**实况**:2026-10-17~18 需求讨论澄清会话产出结论文档,用户令按工作内容维度拆卡、卡片不预占迭代号。五卡已写(顺序=执行顺序):①存档制数据层改造批 ②主菜单三入口化批 ③选档界面新建批 ④营地场景图新建批 ⑤Esc 浮窗与营地顶栏批(各卡估时与内容见卡片自身)。三道复核:grilling 子 Agent 对抗审查(五卡各一)→v1.1;用户裁定「事项按推荐的来」→v1.2;用户两次订正(**卡④留在批内不踢出**;前置归用户自立批,Agent 不代跑不代产——曾越权起草的点选反馈设计稿 v0.1 已撤回删除)→卡④ v2.2;**卡④ 2026-10-21 用户令退回美术设计段、2026-10-31 用户令据 req-card 第〇步重立为 v3.0 现行有效卡(美术设计已闭合=样张过审+art-bible 条款版本 v3.24 §6.2)**。①②③⑤ 零待裁点可编工程波次:②③ 硬耦合同波(「进入存档」钮落点=选档界面),① 是 ③ 前置;⑤ 随 ④ 代码波同行。Alpha 出口裁决(2026-10-21 用户裁):五卡整体列入 Alpha——08「三 · 界面」已开条目,Alpha 出口依赖五卡完工与卡④三个用户侧前置销号。**工程波次一已落地(2026-10-22)**:卡①②③ 随迭代 33 实现批合回 master(合并提交 ebf40947/5c2a7bcf/508f190b,总收口 6e8f72b2),三卡需求卡片与批次产出物已按「吸收即死」退役(设计口径已吸收进 存档序列化.md/生涯.md/camp.md/08-Alpha版本标准.md v1.13/art-bible.md v3.18,反哺落文凭据=文档批 git 提交 1864f157+9f7b3e8a);卡④(营地场景图)退回美术设计段待重立卡、卡⑤(Esc 浮窗)随卡④ 代码波同行,两卡未编排——编入哪一迭代由用户经迭代编排段发令。关联文档:
- 设计澄清定稿:reports/需求澄清-营地交互重做-2026-10-18.md(v1.0:目标体验原话/目标结构树+纵深稿/10 条交互裁决/验收口径/切批原则/遗留待裁)
- 卡④:reports/需求卡片-营地场景图新建批-2026-10-18.md(**v3.0 现行有效卡**,2026-10-31 用户令据 req-card 第〇步重立=美术设计已闭合;样张过审+art-bible 条款版本 v3.24 §6.2,见代码段开工门)
- 卡⑤:reports/需求卡片-ESC浮窗与营地顶栏批-2026-10-18.md
- 卡④:reports/需求卡片-营地场景图新建批-2026-10-18.md(**v3.2 现行有效卡**,2026-10-31 重立 v3.0、2026-11-01 双审修订 v3.1、同日机检预算裁决修订 v3.2=营地六句柄落 ui_camp_modals.gd 子容器;样张过审+art-bible v3.31 §6.2,开工门已全部销号)
- 卡⑤:reports/需求卡片-ESC浮窗与营地顶栏批-2026-10-18.md(**v1.5 现行有效卡**,2026-11-01 双审修订 v1.4、同日机检预算裁决修订 v1.5)
- 卡⑥(前置基建):reports/需求卡片-营地模态句柄子容器批-2026-11-01.md(**v1.0**,2026-11-01 用户令立——营地模态句柄子容器 UiCampModals+camp_overlay 句柄迁移,迭代 47 执行序第一位)
**代码段开工门(/iter 闸门)**:卡④ 已重立为 v3.0 现行有效卡(美术设计已闭合=样张过审 2026-10-31+art-bible 条款版本 v3.24 §6.2,据 req-card 第〇步);原「美术设计闭合后重立卡,再谈编迭代」判据已满足,可编迭代;**重立卡判据=卡头 v2.3 用户令「美术设计闭合(样张过审+art-bible 条款版本)后重立卡,再谈编迭代」**。前置登记(P1–P5 属代码段开工门,非重立卡前置):P1 美术渠道(**2026-10-22 已裁=B 插画级新产**,✦ 渠道作者自跑远程生图管线;规格沿用 art-bible §8.1/§8.2 既有裁决);P2 像素规格(Q6 甲过渡口径已裁,执行归用户线);P3 点选反馈设计稿(用户线,未受命开工;Agent 曾越权起草 v0.1,已按用户令撤回删除;**代码段开工门**);P4 参照物(**2026-10-23 已补**=artwork/2026-10-23-营地场景/Gemini_Generated_Image_ykrc0nykrc0nykrc.jpg,已进 camp-scene-prompts.md §1 风格参考图表、与纵深稿对表、围墙结构冲突已呈报;参照物是参考图,保留 artwork/ 不入游戏目录);P5 GDD/art-bible 落文(文档批,用户发令;**代码段开工门**)。**2026-10-31 用户裁定:保留全部待选稿、各素材第一张样本稿为终稿、「目前我看每个都还可以接受」=样张过审完成;art-bible 条款版本已落 v3.24 §6.2(camp.md v2.2/camp-scene-prompts v1.2/asset-path-map/art-assets-checklist/评估报告同步)——卡④ 重立判据两样已齐。** 终稿指定清单= artwork/candidates/_终稿裁定-2026-10-31.txt(用户准备在游戏内做最终审核)。重立卡已完成(v3.0 现行有效卡);以下为**代码段 `/iter` 开工门**(不阻塞编迭代):P3/P5 两项用户侧前置未销号(P2 Q6 甲口径已裁不阻塞写卡、终档置换归反哺点)、camp-scene-prompts §5 五个待裁点(帐篷尺寸/门扇高宽/布告板位置/描边颜色追认/环境子板色值)未清零;十张终稿仍为原始候选未做后处理未落 `game/assets/art/`,此属实现段 T1/素材录入(asset-ingest 第 3 步终稿拷入),用户 2026-10-31 令只生成素材不做后续,开工时另发令。
**代码段开工门(/iter 闸门)**:卡④ 已重立为 v3.0 现行有效卡(美术设计已闭合=样张过审 2026-10-31+art-bible 条款版本 v3.24 §6.2,据 req-card 第〇步);原「美术设计闭合后重立卡,再谈编迭代」判据已满足,可编迭代;**重立卡判据=卡头 v2.3 用户令「美术设计闭合(样张过审+art-bible 条款版本)后重立卡,再谈编迭代」**。前置登记(P1–P5 属代码段开工门,非重立卡前置):P1 美术渠道(**2026-10-22 已裁=B 插画级新产**,✦ 渠道作者自跑远程生图管线;规格沿用 art-bible §8.1/§8.2 既有裁决);P2 像素规格(Q6 甲过渡口径已裁,执行归用户线);P3 点选反馈设计稿(用户线,未受命开工;Agent 曾越权起草 v0.1,已按用户令撤回删除;**代码段开工门**);P4 参照物(**2026-10-23 已补**=artwork/2026-10-23-营地场景/Gemini_Generated_Image_ykrc0nykrc0nykrc.jpg,已进 camp-scene-prompts.md §1 风格参考图表、与纵深稿对表、围墙结构冲突已呈报;参照物是参考图,保留 artwork/ 不入游戏目录);P5 GDD/art-bible 落文(文档批,用户发令;**代码段开工门**)。**2026-10-31 用户裁定:保留全部待选稿、各素材第一张样本稿为终稿、「目前我看每个都还可以接受」=样张过审完成;art-bible 条款版本已落 v3.24 §6.2(camp.md v2.2/camp-scene-prompts v1.2/asset-path-map/art-assets-checklist/评估报告同步)——卡④ 重立判据两样已齐。** 终稿指定清单= artwork/candidates/_终稿裁定-2026-10-31.txt(用户准备在游戏内做最终审核)。重立卡已完成(v3.0 现行有效卡)。**2026-11-01 用户令修订换代**:卡④ v3.1→v3.2、卡⑤ v1.4→v1.5(双审修订+机检预算裁决——营地六句柄落 ui_camp_modals.gd 子容器,卡⑥ 前置建件);**代码段 `/iter` 开工门已全部裁断销号**——P3 销号口径=camp.md v2.2 四态规格即设计稿;P5 销号口径=形态落文完成、实装回填归反哺点(camp-scene-prompts v1.3 §5 已改已裁+art-bible v3.31 §6.2 同步,本会话文档批落文);十张终稿后处理授权随 `/iter` 开工令一并生效(不再两次发令)。**开工门清零,迭代 47 可开工,执行序=卡⑥ 句柄子容器 → 卡④ → 卡⑤。**
**改动面**:文档+游戏代码+美术素材
**状态**:文档设计已闭合+美术设计已闭合+已写卡+已编迭代 47
#### 模态句柄册分域重构(2026-11-01 登记,来源=会话提及)
**实况**:重构证据=`game/presentation/ui_modals.gd:7-20`(类变量 14 实测)+停机呈报-迭代47机检预算互斥-2026-11-01.md(卡④⑤ 平铺合计 19>S13 上限 15)——按技术角色平铺 13 模态句柄+flow,迭代 47 营地域新增六句柄顶格 S13 类变量上限 15(2026-11-01 用户裁:最小改动先走——营地六句柄落 `ui_camp_modals.gd` 子容器,随迭代 47 卡⑥/卡④/卡⑤ 落地,本条只登记**全量分家**)。目标形态=存量句柄按域分组抽容器(局内 run 六件/菜单与跨语境归组)+ModalFlow 自 UiModals 升格独立持有;行为零变化、纯句柄搬家;返工面=全仓探针深员寻址+`ui_registry.gd node()` 寻址+`main_ui_build.gd` 装配。拆分判据权威=code-conventions §1.1(沿职责轴;营地子容器先例=`ui_camp_modals.gd`,随迭代 47 落地后为准)。
**改动面**:游戏代码
**状态**:待写卡