Compare commits

...
64 Commits
Author SHA1 Message Date
DKChan a5c89d6773 合并(迭代47批后收尾): 反哺点十二条处置闭合——落文八处(architecture v1.28营地域六件行+camp.md v2.4顶栏实装+art-bible v3.35围墙实装值与32色子板行+camp-scene-prompts v1.4落位实测+生涯v1.4 P6甲落地+08-Alpha v1.23 Esc例外+asset-path-map与美术清单入库确认)+shotchecker判据互斥随裁决在案零落文面+两未决迁待办新条(palette_check双门机检化/CAMP_HEADER_BAND令牌退役评估)+目检五项登记体验验证清单㊺+产出物退役九件(三卡/反哺点/两报告/需求澄清/美术方案提案/双审复核)+待办清单营地条整删两注记+roadmap迭代47行已完成与当前位换代(迭代1-47全完成)+README能力行改名回表四处(收尾核对义务)+R3句式两处; 文档域闸验收通过与基线一致(回执bd54d1f4); 合回后post-merge钩子自动跑CI全量域闸,回执后台挂ci_watch 2026-11-02 00:24:39 +08:00
DKChan bd54d1f46d 收尾(迭代47批后收尾): 反哺点十二条处置闭合——①落文八处: architecture v1.28(营地域六件归属行增补——UiCampModals/CampSceneView/营地四子界面视图族/camp_esc_overlay+MainEscFlow/CampHudBand/CampRefreshBus;D1行营地面现状化四柜台退役;ui_registry行子容器与层序合同槽换代camp_overlay退役换camp_scene+四子界面+camp_esc_overlay;产品码首个键盘输入入口main._unhandled_input契约9注记)+camp.md v2.4(顶栏刷新单口实装回填——进档与写口尾触发现开界面与HUD同步取现值;叠层节卡④⑤引用随手清)+art-bible v3.35(§6.2围墙细条句实装落盘值(0,261,1280,49)底锚y310·桩身43锁定·备选A'存档在案;§4.1营地场景环境子板32色行——并集272收敛32违例对0,色值事实源=入包量化产物,换算式登记无逐色值差异呈报)+camp-scene-prompts v1.4(八物件落位实测contain表——board130x121/gate240x211/dummy120x180/forge240x193/tent251x170/chest106x90/notebook100x66/campfire160x149,锚定底中心+顶锚;量化缩放实况注记——32色重量化+非整数倍contain换算判据口径)+生涯.md v1.4(营地行条带撤下现状化+P6甲落地节换代+期二条带处置义务清空)+08-Alpha v1.23(键盘手柄行Esc例外注记+营地两行在役换代+需求澄清死链改git沿革)+asset-path-map与美术清单§3.5(营地十件实装入库确认scene_camp_*十件在役TEX_CAMP_*十键消费+终审置换同径注记);②反哺#10 shot_checker色数判据互斥已随82b2cbea/8e294de9用户裁决落地(阈值40/camp特判删除/色数收敛待办新条91f91744)零落文面;③反哺#12 CAMP_HEADER_BAND令牌退役评估迁待办新条(随令牌批处置非独立强删)+palette_check基线建档与双门机检化迁待办新条(实现报告§7.2未决先迁);④目检五项登记体验验证清单㊺(场景观感含十件素材终审观测/七物件点选反馈/大门篝火动线/Esc三动线与HUD/四子界面视觉);⑤产出物退役九件(吸收即死+证据只留最新): iter47反哺点+实现报告+验收审计+三需求卡片(合回3443890d)+需求澄清(08-Alpha引用改git沿革)+营地场景图美术生成方案提案(camp.md:31留痕句改git沿革)+卡④⑤双审复核(遗留节零未决);提案-营地色彩与氛围脚本按art-bible§6.2/camp-scene-prompts依据行活引用保留;⑥账面: 待办清单营地交互重做条整条删除(五卡全落地状态机出口)+UI贴图重构条与图鉴条落地注记+两条新登记,roadmap迭代47行已完成(2026-11-01)+两列退役保留字+当前位换代(迭代1-47全完成,下一步=迭代48待/iter发令);⑦闸红修复三处: gdd_lint R3两处(08-Alpha合回+日期句式去修订注记形态)+capability_audit四红(收尾核对改名回表义务——README:144/148真实入口object_pressed信号改锚CampSceneView._gui_input()/README:145计划名_on_confirm_new_adventure改落地名_on_confirm_accepted与main._resume_from_menu/README:146计划用例_test_forge_rows改落地名_run)〔纯测试清单维护〕 2026-11-02 00:24:14 +08:00
DKChan bfa0a8bed3 合并(工程+规则·CI回执等待异步化·用户令): ci_watch.sh新件+process-conventions§1.3回执等待纪律+AGENTS.md运行与验证一句; iter技能总收口步同口径换代(.zcode豁免面主区直改,不入本笔)——合回落地先呈报本批总结,回执等待一律run_in_background挂ci_watch.sh,禁前台sleep/轮询;两会话实证(合并→呈报阻塞20.5分钟/全天前台sleep等闸); 合回后post-merge钩子自动跑CI全量域闸 2026-11-02 00:11:05 +08:00
DKChan e340f14e87 工程+规则(CI回执等待异步化·用户令): ci_watch.sh新件(认领判据=回执commit为合并提交或后代+回执时刻晚于合并,半截JSON重试,900s超时防挂死,经git公共目录恒读主区回执)+process-conventions§1.3回执等待纪律+AGENTS.md运行与验证一句+iter技能总收口步换代——合回落地先呈报总结,回执等待一律run_in_background后台任务,禁前台sleep/轮询;两会话实证(合并→呈报阻塞20.5分钟/全天前台sleep等闸) 2026-11-02 00:10:25 +08:00
DKChan de5e31ed55 合并(迭代46批后收尾): 反哺点落文七处(奇物v1.4/行动经济v1.4/战斗结算v1.5/地图战斗资源v1.4/architecture v1.27/art-bible v3.34/battle.md下限句退役换锚点判据)+目检四项登记体验验证清单㊹+产出物退役十一件(三卡/两反哺点/两报告/两审计/停机呈报/卡B复核)+待办清单三条目整删+roadmap迭代46行已完成与当前位换代+art-bible入库合并冲突标记修复+refresh_feats_test补登记run_all.gd(02全量漏件补位,能力行代码面锚定成立); 文档域闸验收通过与基线一致(回执73fc11eb); 合回后post-merge钩子自动跑CI全量域闸 2026-11-01 23:52:05 +08:00
DKChan 73fc11eba4 收尾(迭代46批后收尾): 反哺点九条处置闭合——①落文七处: 奇物v1.4(换图系七件设计声明改已实装——节标题/基线不变句/件表构成句30→37行三代换,稀有度14/13/10/0·cap均1入句)+行动经济v1.4(改写节标题已实装换代+折后价floor取整与门槛扣费同源细则+调参表两行已入feats.tres)+战斗结算v1.5(换图系节已实装换代+board_refreshed统一事实挂点句+全局受伤事件四路径统一经唯一发射口与消费族改述——含护盾吸收在内既有订阅对四路径同等生效)+地图战斗资源v1.4(清图奖励只在清图批兑付句——弃图刷新不触发清图事件/换图新图不发清图奖励/刷新专长GP走战斗内缓冲)+architecture v1.27(BattleRefreshFlow行增补——换图刷新流程件可用性门/折后价单点/执行序/成功事实登记,S7_B1_SET增补在案;damage_controller行现状化——唯一发射口唯一消费族四路径统一;action_economy行现状化——dig_paid命中单点+request_refresh收折后价入参;GrantSegment句现状化——成功刷新事实消费+轮末治疗计数兑付面)+art-bible v3.34(§4.1换色合并登记①②实况化——STAND_STONE_RIM/EDGE源常量已删、#4C4038补代码名PLAZA_EDGE;§8.2高墙口沿高光Color8(196,172,132)→#C9AF83即BRICK)+battle.md(棋盘格尺寸公式可读下限30px句退役——换锚点判据句:可读性以用户验收现行画面为锚、全域禁人造阈值、结构钳制[26,46]管算式、接线守卫=生产写入值与单点算式严格相等、最大板推定读数留痕不作判红);②术语表零新增(本批新名均属代码标识符);③10-31清单第8条算例表改数不执行——算例表已随布局几何单点化批退役无落点;④art-bible v3.33入库合并冲突标记顺手修复(HEAD/docs-mood-ban两侧同文取一);⑤目检四项登记体验验证清单㊹(卡A刷新动机观感/卡B名字行14px底线/口沿换值/回退描边;卡C14×14可辨读性已随锚点裁定用户实机背书免登记);⑥产出物退役十一件(吸收即死+证据只留最新): iter46两反哺点清单+两实现报告+两验收审计+三需求卡片(卡A/B合回34bb62a5/卡C合回ab5a45af)+停机呈报(裁决已落+卡Cv2.0落地被取代)+卡B grilling复核报告——判据全过: 反哺点全落文、遗留读数义务零(卡C§七/卡AB§十已了结)、引用面=roadmap行保留字+待办清单条目随整删出清;⑦账面: 待办清单三条目整条删除(迭代43批后待裁两点/封锁-蓄力combo·换图系七件/棋盘格边长三方取值——三卡合回,状态机出口),roadmap迭代46行已完成(2026-11-01)+卡片报告两列退役保留字+当前位换代(迭代1-46全完成,下一步=迭代47待/iter发令);⑧闸红修复两件: gdd_lint R3 roadmap收尾叙述去「作废」字样(退役无落点句改述)+capability_audit 11红——refresh_feats_test.gd原经卡片申报走02关联档选跑未登记run_all.gd清单,卡片退役后能力行代码面锚定失效且02全量漏件,补登记进套件清单(expect=0)由全量CI承接〔纯测试维护〕;提案-弃图刷新系专长流派按用户前令保留不入退役 2026-11-01 23:51:46 +08:00
DKChan 3443890df2 合并(迭代47·营地场景图批+Esc浮窗与顶栏批+句柄子容器批): 三卡收口——营地场景图十层落地(CampSceneView+layout表fail-fast+十件素材)/四柜台拆解五子界面+CampRefreshBus单口+CampOverlay退役/Esc浮窗四态路由+HUD栏+设置语境分支/camp.md v2.3定稿吸收(半透明遮罩0.6场景根留底/Esc先关窗);独立验收审计通过(处置后);--code闸全绿(88d1344d对齐);合回后post-merge钩子自动跑CI全量域闸 2026-11-01 23:44:56 +08:00
DKChan c620214e06 iter47-收尾:迭代48编排合入(纯文档面): roadmap迭代48行+待办清单状态——纯文档收尾 2026-11-01 23:44:29 +08:00
DKChan 6208a9cd1a iter47-卡3:卡⑤(迭代47·Esc浮窗与营地顶栏批): Esc浮窗四态路由+CampHudBand顶栏栏+设置语境分支+断言批+审计补差+master取用+camp.md v2.3定稿吸收(半透明遮罩0.6场景根留底/Esc先关窗Q1甲换代)——终态六枚落满 2026-11-01 23:44:29 +08:00
DKChan bbeeea77e0 iter47-卡2:卡④(迭代47·营地场景图新建批): 十层素材落地+CampSceneView场景图+layout表fail-fast+四柜台拆解五子界面+大门篝火动线+断言迁移批+审计补差——CampOverlay退役全仓零残留 2026-11-01 23:44:29 +08:00
DKChan 0761071b37 iter47-卡1:卡⑥(迭代47·营地模态句柄子容器批): UiCampModals子容器+camp_overlay句柄迁移modals.camp.overlay二层——43处改指零行为变化,--code闸all_green 2026-11-01 23:44:29 +08:00
DKChan eff87256b6 合并(迭代48编排·用户令): 三卡编入——状态合并累加制+立绘显示档批+战斗区背景替换合计≈6.1工作日常规带; 卡序核心域先行表现殿后; 累加卡对照表能力行格机检口径顺手修(闸红5→0); 三条目状态已编迭代48; pipeline_gate PASS 2026-11-01 23:29:32 +08:00
DKChan 261ff5b615 编排(迭代48·用户令): 三卡编入——状态合并累加制(中值≈1.125d)/立绘显示档批(≈0.85d)/战斗区背景替换(中值≈4.125d),合计≈6.1个工作日常规带零特批; 卡序=核心域先行(EV锚L3预案早曝)表现两卡殿后集中目检; 三条目状态推进已编迭代48; pipeline_gate PASS(44条目/48总表行/7交叉链/11卡全过) 2026-11-01 23:29:30 +08:00
DKChan 0657370be9 修卡(状态合并累加制批·流水线闸格式): 对照表能力行格换代机检口径——R2-R5「同 R1 新增行」简写改全称「新增:同名状态合并策略(叠加/累加/并存)与剩余次数语义」、R6 既有名补 README 在册全称; 闸红5→0 2026-11-01 23:29:28 +08:00
DKChan 240448be7f 合并(art-bible版本行订正): v3.32→v3.33——合并冲突解消笔误一行订正,内容零变化 2026-11-01 23:24:45 +08:00
DKChan f0f5d8b437 文档(art-bible版本行订正 v3.32→v3.33): 合并冲突解消笔误——v3.33 重编在案(bfea1475),合并提交 c94c2108 版本行误取 HEAD 侧 v3.32;一行订正,内容零变化 2026-11-01 23:24:44 +08:00
DKChan c94c2108e3 合并(文档批·art-bible§2情绪明暗禁令删除+营地五界面窗口化定稿): v3.33——禁令整块删除(明亮基调正向方向,压暗逐案样张目检)+引用网九处随清+camp.md v2.3叠层开合定稿(五功能窗口/遮罩0.6/互斥单开/Esc先关窗)+待办营地五界面条目文档设计已闭合; 与立绘批v3.32撞号已重编v3.33(冲突=版本行,取v3.33); 合回后post-merge钩子自动跑CI全量域闸 2026-11-01 23:21:49 +08:00
DKChan f4902bc746 合并(写卡批·状态合并累加制): 需求卡片-状态合并累加制批-2026-11-01 v1.1双审齐(grilling两轮+godot-master)零待裁点; README能力行新增同名状态合并策略+覆盖域表更新; 待办条目推进文档设计已闭合+已写卡; 合回后post-merge钩子自动跑CI全量域闸 2026-11-01 23:20:47 +08:00
DKChan bfea147581 版本重编(art-bible v3.32→v3.33): 与立绘显示档批的独立 v3.32 撞号——两批内容零重叠、版本身份标记不可共享,本批(§2禁令删除批)合回前重编 v3.33 2026-11-01 23:20:39 +08:00
DKChan 8e294de976 合并(闸修·shot_checker色数阈值40+色数收敛待办登记): 用户裁阈值40(正常实测地板60的2/3)+camp特判删除+std≥8名分补注;正常屏色数过杂登记待办治理;迭代47侧merge master取终态,T6重跑即真营地复跑验证 2026-11-01 23:17:12 +08:00
DKChan 91f91744cd 登记(待办清单·用户令): 新条目「正常屏唯一色数收敛」——七屏实测60~1579色数过杂记治理,待美术设计讨论(目标上限/收敛手段/与§4.1两层色板条款关系),状态待文档设计,改动面文档+美术素材;本批只调闸阈值不动色板 2026-11-01 23:17:04 +08:00
DKChan c3206f86b2 闸修(shot_checker色数阈值64→40·用户裁): A段唯一色数改正常实测地板余量制——现行七屏实测60(营地场景图,七屏最低)~1579,阈值40=地板2/3,低于一切正常屏;行内语义改「色数异常回落」,camp=32特判随零特判令删除;头注补双名分(std≥8=圣经§7.2文字4.5:1推论下界之下+正常实测最低19.3之上的空屏绊线;色数40=正常地板余量);art-pipeline-tools口径行同步 2026-11-01 23:17:02 +08:00
DKChan f33f9c1cfe 合并(立绘显示档批·美术设计线闭合+卡片 v1.1 双审订正): art-bible v3.32 站桩立绘显示档三档绝对高 96/144/188+源=原稿像素网格禁入包降采样、08 v1.22 覆盖行、art-prompts v2.13 §5.4 按档派生工单、character-design 与两张美术清单随动、待办条目推进「美术设计已闭合+已写卡」、reports 卡片+双审报告入库、能力清单补「站桩显示档与素材分辨率绑定」行。本批只落设计与卡片,代码与素材实装待 /iter 发令。合回即由 post-merge 钩子后台跑本地 CI 全量域闸。 2026-11-01 23:02:34 +08:00
DKChan fc30e984c6 写卡(立绘显示档批 v1.1·grilling+godot-master 双审后执行级订正): reports/需求卡片-立绘显示档批-2026-11-01.md 新立(T0–T3 估 0.85d、零待裁点、验收 V13–V18 绑显示档高/纵横倍率/源高钉值/键集自检)+reports/复核-双审-...md 双审报告。阻断两条已补卡:①原稿目录不入 git 且 sync_worktree_assets 不同步 artwork、pixelize 缺件静默空跑 exit 0——T1 补显式主区根路径+产物计数自证=9;②素材不入 git、worktree→主区无同步通道且 standee_asset_check 不在闸清单,合回后敌侧落位无人看守——T3 补「九件落递主区再触发 CI」收口步、V13 逐件钉源高防旧降采样稿蒙混。其余订正:样张 A4 硬锁 46×48 与 96×96 方框列入解冻面、主角源明写切 2× 稿、get(key,96.0) 兜底违反自卡契约 2/6 改 push_error+V18 键集自检、灰矮人 0.76× 与地图头像 0.52× 补已接受项并点名目检、T1/T3 闸件口径时点三处互斥统一为同批、注释随动面补全(含反向引用已注销禁令的旧句)、§14 注销「核测试键登记值」空操作(gdstyle 不扫 tests)、浮点判等一律 is_equal_approx、两表对账双审结论相左故 V17 真断言+读不到走 L2 禁当红禁当绿。能力清单补行「站桩显示档与素材分辨率绑定」(写卡段义务,代表用例 _check_display_tier)。待办条目状态推进「美术设计已闭合+已写卡」。闸况:pipeline_gate PASS(9 卡)、--docs 零新增红。 2026-11-01 23:02:00 +08:00
DKChan 25b2a0bd33 文档设计(立绘显示档·美术设计线闭合): art-bible v3.32——§8.2「站桩立绘源档 36–94、显示 ×2」条换代为「站桩立绘显示档=三档绝对高 96/144/188」+源=原稿像素网格、入包禁再降采样、显示倍率=档高÷源高(NEAREST)、体量顺序由显示档承载不由源高承载、放大与超宽登记为已接受项;排版条 >150 溢出举例随动(食人魔入大档);槽位条「高度的整数倍档」改「高度的绝对档(档高取整数像素值)」消与倍率制的字面冲突;§8.3③ 闸规格改按显示档校验。character-design §5.5 指路行换代。08 v1.22 覆盖行改「立绘显示清晰度(原稿分辨率+三档显示)」,出口=9 件按档落位+目检。art-prompts v2.13 §5.4 手绘重产工单改按档派生工单(九件、零绘制、色值以现役稿为准)+产出状态随动。两张美术清单站桩行随动。根因凭据=现行入包稿与原稿 dom_block_resize 产物逐像素相同(diff 空),细节损失属入包前降采样而非形象工艺。2026-10-14「须手绘重产」方向经用户裁作废、现有形象即终稿;#A79CB8 保留在板本批不消费、色板额度议题另批。 2026-11-01 23:01:40 +08:00
DKChan 82b2cbeaff 合并(闸修·shot_checker camp屏色数判据): 三读法裁决A案落地——camp屏唯一色数阈值64→32对齐场景环境子板32色上限(环境层在场语义,防空白std≥8不动),art-pipeline-tools口径行同步;迭代47侧merge master取用,T6重跑即真营地复跑验证 2026-11-01 20:26:53 +08:00
DKChan 4f52e288b1 闸修(shot_checker·camp屏唯一色数阈值64→32·停机呈报三读法裁决A案): 营地场景图批渲染闸红(std=55.6/colors=60)与art-bible§4.1场景环境子板每屏≤32色硬上限互斥——A段camp屏阈值分屏化取子板上限值32(环境层在场语义:未渲染只剩UI≈28色即触红),其余屏64不动,防空白语义std≥8不变;art-pipeline-tools口径行同步;tools/闸件工程改动直改通道(用户委托裁决) 2026-11-01 20:26:45 +08:00
DKChan a93e6c0005 修卡(状态合并累加制批·godot-master审查线补齐): 必改1+注5落卡——补第三处refresh语义断言申报(damage_controller_test:376标签换代,行为面仍绿)/布尔受理行定性改attack_up(accumulate行)/探针四段式改五段式/改名行号清单补:159调用点/tres保持CRLF/孤立验证五套件扩六(EV锚套件round_flow提前曝红);引擎语义核验零阻塞(容器改写无缝/S9零新键/加载期fail-fast属实/测试harness全兼容) 2026-11-01 19:51:46 +08:00
DKChan dda2238f32 文档(art-bible§2情绪明暗禁令删除+营地五界面窗口化定稿·用户令): §2禁令整块删除——用户裁明亮基调为正向方向,压暗属普通设计决策逐案走样张目检(禁令查无正向拦截实例,三道硬闸在位:基调正向句/冻结样张/明度三档带机检); 引用网九处随清(§2格底尾句/结算三拍§2禁令标签去签设计零变化/§3.2砖缘豁免声明/§6.1背景/§6.3迷雾自查段/§9.1取弃表/§9.2反面行改风格基准非禁令/character-design提灯允许域/checklist封印特效/battle·level-map明度分层引用句换代); art-bible v3.31→v3.32; camp.md v2.2→v2.3叠层与开合定稿=五功能窗口·场景根留底+半透明压暗遮罩(复用模态遮罩件0.6锚)+五窗互斥单开+Esc先关窗后浮窗四态+各窗关闭钮; 待办营地五界面条目缺口消解状态推进文档设计已闭合(零新美术,实现卡强依赖卡④完工) 2026-11-01 19:01:07 +08:00
DKChan a9c006e7d1 写卡(状态合并累加制批·grilling两轮复核): 卡v1.0零待裁点——六行合并策略refresh→accumulate累加制实装卡(T0基线读数/T1表行+词表+容器累加臂+V1-V3断言与换代申报三处/T2产出物落位+独立验收审计); EV锚红L3停机条款(重锚归用户档位); 表批注零触碰(4a2ed961用户恢复令在案,范围边界在卡); 待办条目状态推进文档设计已闭合+已写卡; README能力行新增同名状态合并策略+覆盖域表增益状态行更新 2026-11-01 08:23:12 +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
DKChan 5269ed4080 合并(fv25退役与欠账登记批): 〔fv25〕表内注释存在性断言退役——守载体不守信息,行留任仍由fv12从解析数据守,02套件67→66项;待办清单登记「hobgoblin 内容三件套补齐」条目(2026-11-01/来源=审计发现/状态=待文档设计,凭据=敌人系统.md v1.1)——欠账登记迁待办清单唯一登记处,禁再以表内注释+机检承载;用户令工程改动经worktree车道合回,合回后post-merge钩子自动跑CI全量域闸 2026-11-01 02:11:16 +08:00
DKChan 5ecfabc251 Merge branch 'master' into fv25退役 2026-11-01 02:11:05 +08:00
DKChan 57084e0bd3 工程+登记(fv25退役与欠账登记批·用户令): 删 table_structure_guard_test〔fv25〕断言——守「表内注释存在性」与游戏行为无关(行留任仍由fv12从解析数据守,注释机检守载体不守信息,用户裁无验证价值),连带摘fv12消息尾缀〔fv25〕与②块注释「缺口登记随行」句,02套件67→66项;登记待办清单「hobgoblin 内容三件套补齐」条目(2026-11-01/来源=审计发现/状态=待文档设计,凭据=敌人系统.md v1.1「hobgoblin缺口登记」节2026-10-13迭代24反哺④)——欠账登记迁待办清单唯一登记处,禁再以表内注释+机检承载;GDD正本登记不动 2026-11-01 02:11:04 +08:00
DKChan 12af4fe00c 合并(合回即删改回批·用户令): 删区时机改回「合回即删」2026-09-19原裁——「CI全量绿→删区」系10-30本地CI化批夹带改动未经用户单独裁断;红态修复车道换轨新开fix-修复worktree(免重塑免数卡),追绿复验落post-merge(post-commit降兜底);规则文本五文件十一处随动+iter/.qoder技能豁免面直改;件核旧语义零残留;合并落地即删区=新规首次实操 2026-11-01 02:09:46 +08:00
DKChan 22809f9b79 工程+文档(合回即删改回批·用户令): 删区时机改回2026-09-19原裁「合回即删」——「CI全量绿→删区」系10-30本地CI化批(c91bbddd)重写§1时夹带、未经用户单独裁断,10-31车道恢复批(94628fca)沿用未复位;恢复收口=冻结→件核→并master→重塑→主区merge --no-ff→合回落地即git worktree remove+删分支,CI由post-merge钩子在合回落地时自动触发、与删区解耦;红态修复车道换轨=新开修复worktree(fix-<任务名>分支,非iter<N>形免重塑免数卡,修复归属原批卡片)修复合回追绿,追绿复验落post-merge,post-commit钩子降为红态主区直提兜底触发(机件零动);规则文本五文件十一处随动:AGENTS车道铁律行/运行与验证首条/改动四类行,process-conventions §1.2收口序列与红态句/§1.3/§1.9①,test-conventions §4,iter技能两处+.qoder镜像两处(.zcode/.qoder豁免面主区直改不入git);件核=旧语义全库grep零残留,守卫数卡提示「回任务worktree」语义仍准(拦截时任务区尚在)零动 2026-11-01 02:09:29 +08:00
DKChan 94dfa7e79b 合并(编排批): 卡C棋盘格边长口径收口批v2.0回编迭代46——迭代行卡列补卡C路径/编入时间换代2026-11-01/预计周期≈8.625不变/状态保持进行中(用户裁);待办条目状态追加已编迭代46;当前位换代=卡C v2.0待/iter发令回worktree iter46续工+迭代47已编排待开工;battle.md:144退役归文档批待发令;文档域闸绿(7fd50fc4) 2026-11-01 02:08:41 +08:00
DKChan 7fd50fc485 编排(卡C v2.0回编迭代46): 棋盘格边长口径收口批v2.0(2026-11-01用户裁锚点换代后重立,写卡批e7090bc9)重编入迭代46——迭代行卡列补卡C路径、编入时间换代2026-11-01、预计周期≈8.625不变;状态保持进行中(用户裁,卡A/卡B已合回34bb62a5、剩余卡C约0.5日);待办条目状态追加已编迭代46(已写卡段保留);当前位下一步换代=卡C v2.0待/iter发令回worktree iter46续工(WIP两件复用一件返工)+battle.md:144退役归文档批待发令+迭代47已编排待开工;当前位措辞避R3修订注记形态(gdd_lint);流水线闸PASS 2026-11-01 02:08:06 +08:00
DKChan e7090bc9c1 合并(写卡批): 卡C棋盘格边长口径收口批v2.0重立合回——2026-11-01用户裁锚点换代(可读性锚点=用户验收的现行渲染值,基座档14×14=28px达标;可读下限30px句与探针36px人造阈值全退役,MIN_CELL手抄常量删除);探针判据换锚=实读与单点算式_cell_for严格相等+结构钳制+13/14推定留痕;grilling独立复核四项打回全落(S13实测更正max-class-variables=31/推定死断言删除/重构类两契约条款补齐/对拍不一致归L3#2);文档域闸绿(3a8634ab);battle.md:144退役落文归文档批待发令 2026-11-01 01:53:44 +08:00
DKChan 3a8634ab92 写卡(卡C v2.0·grilling复核修正): 独立复核四项打回全落——①§3补两条重构类必填条款(禁为机检文本保留无效成员/拆分边界纪律四要件,判据权威=code-conventions §1.1);②S13数字簇实测更正max-class-variables 32→31(baseline:11),连带§1/§9L3#1升32/§13/T4四处;③删对照表行4推定est≤0死断言(clampi兜底下数学不可达,正中test-conventions§8.4多层防御禁则),推定有效性归零尺寸前置;④推定读数对拍不一致处置句=§9L3#2呈报禁实现侧改锚 2026-11-01 01:53:22 +08:00
DKChan 92e9c70a8d 合并(表批注误删恢复批·用户令): 18 张数值表批注全量恢复——10-31 清理批系对用户指令的误读(文档内容误读为代码配置文件批注),经用户令回滚;恢复源=263050ad,feats 以现行内容回插原位 11 行,逐表校验内容行与现行零变化 2026-11-01 01:42:57 +08:00
DKChan 4a2ed96182 工程(表批注误删恢复批·用户令): 18 张数值表 ; 批注全量原样恢复——10-31 表批注清理批系对用户指令的理解错误(用户「表批注相关的东西放到 git 记录」指文档内容,误读为代码配置文件批注并经错误框架的选项确认执行),经用户令全量回滚;恢复源=263050ad(删除前末版),其中 feats.tres 以现行内容(迭代46卡A换图系七件新增行)为基底回插原位 11 行批注,adventure/battle_layout 原本无批注零恢复;逐表校验=内容行与合回现行零变化、仅批注行差异 2026-11-01 01:42:22 +08:00
DKChan 33525d19de 写卡(棋盘格边长口径收口批·v2.0重立): 2026-11-01用户裁锚点换代——可读性锚点=用户验收的现行渲染值(基座档14×14=28px达标),battle.md可读下限30px句与探针36px人造阈值全部退役(MIN_CELL手抄常量删除,全仓禁第二份阈值抄本);探针判据换锚=实读cell_size与单点算式_cell_for严格相等+结构钳制[26,46]+13/14推定留痕(≤0退化判据,非阈值);v1的探针门槛36→30方向作废,WIP三件复用判定=算式单点/四档冒烟原样复用+探针件返工;§2现状重锚2026-11-01(算例表已随1cdef6ba退役/布局事实源=battle_layout.tres五字段/实测读数40/33/30/28与46顶格为锚点);同步面battle.md:144退役走反哺点通道(归文档批);零待裁点,U0开工门 2026-11-01 01:39:59 +08:00
DKChan 3499638dee merge(迭代47编排): 营地交互重做卡④营地场景图新建批+卡⑤ESC浮窗与营地顶栏批同编迭代47——整批≈14.25个工作日经用户裁授带日期特批标记;待办清单条目状态追加已编迭代47;流水线闸PASS 2026-11-01 01:18:21 +08:00
DKChan e075f92e0f 编排(迭代47·营地交互重做卡④⑤整批): 卡④营地场景图新建批(≈10.5日)+卡⑤ESC浮窗与营地顶栏批(≈3.75日)同编迭代47,执行序卡④→卡⑤,合计≈14.25个工作日超10日经用户裁整批并授带日期特批标记;待办清单「营地交互重做·五张需求卡片」条目状态追加「已编迭代47」(已写卡段与卡片路径原样保留);流水线闸PASS(迭代总表47行/卡片路径8条全在场) 2026-11-01 00:13:11 +08:00
DKChan 34bb62a55b 合并(迭代46·拆批): 卡A换图系双基座七件批+卡B站桩名字行提档与地台色合并批合回——独立验收审计通过零缺漏(对照表V1-V12与卡B六行逐行核·八套件自跑全绿·削弱全申报面);卡C棋盘格边长口径收口批经用户裁拆批退回写卡段(触发卡片L3#2停机点,停机呈报+待办缺口在案);合回后post-merge钩子自动跑CI全量域闸 2026-10-31 18:36:13 +08:00
DKChan ceb816cd39 merge(master): 车道恢复批并入(worktree铁律回归+pre-commit直改闸退役,94628fca) 2026-10-31 18:34:33 +08:00
DKChan eb5dc1e7cb iter46-收尾 2026-10-31 15:13:32 +08:00
DKChan aba54ab228 iter46-卡2:名字行提档FS_HINT14+地台色合并: 退役两常量改指BRICK/PLAZA_EDGE+golden三张0.00%留痕 2026-10-31 15:13:32 +08:00
DKChan 3fa99583fb iter46-卡1:换图系双基座七件批: 七件实装+全局damage_taken统一+BattleRefreshFlow拆件+词表扩签+数据37行——代码域闸全绿 2026-10-31 15:13:32 +08:00
133 changed files with 4458 additions and 3414 deletions

No files matched your search

+3 -3
View File
@@ -16,7 +16,7 @@
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
- **合回验证 = 本地 CI(2026-10-30 用户裁)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh` 跑一次全量域闸(调 `tools/run_all_gates.sh` 不带参数档,02 全套件),结果落 `game/tests/.gates/ci-status.json`(commit/绿红/红闸名单+关键日志行/耗时)+`ci-history.jsonl` 流水账;CI 红态下每个修复提交经 post-commit 钩子(`tools/hooks/post-commit`)自动追绿复验,绿态与无状态零触发——批内提交零钩子检查、合回只验一次,同一份字节禁止第二次人手跑。CI 红不回滚合并,红项在案、修复提交追绿;开工先读 CI 状态,红未修不开新批。跑批起手自动 `--editor --quit` 刷类名缓存(.godot 缓存不入 git,防合并后失配假红)。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起
- **合回验证 = 本地 CI(2026-10-30 用户裁)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh` 跑一次全量域闸(调 `tools/run_all_gates.sh` 不带参数档,02 全套件),结果落 `game/tests/.gates/ci-status.json`(commit/绿红/红闸名单+关键日志行/耗时)+`ci-history.jsonl` 流水账;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(红态修复走新开修复 worktree,`fix-<任务名>` 分支,免重塑免数卡;post-commit 钩子保留为红态主区直提兜底触发,绿态与无状态零触发)——批内提交零钩子检查、合回只验一次,同一份字节禁止第二次人手跑。CI 红不回滚合并,红项在案、修复合回追绿;开工先读 CI 状态,红未修不开新批。跑批起手自动 `--editor --quit` 刷类名缓存(.godot 缓存不入 git,防合并后失配假红)。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**回执等待一律后台(2026-11-02 用户裁)**:合回落地先向用户呈报本批总结,随即 run_in_background 运行 `tools/ci_watch.sh <合并提交>` 等回执、唤醒后再续,禁前台 sleep/轮询等 CI(细则=process-conventions §1 第 3 条)
- 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd`
- 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`)
@@ -93,11 +93,11 @@
## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约)
- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→CI 全量绿→删区,CI 红回任务 worktree 修复合回;主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后本地 CI(`tools/hooks/post-merge` 触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后本地 CI(`tools/hooks/post-merge` 触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
- **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节)
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
- 合回后验证 = 本地 CI 全量域闸全绿(唯一强制验证点;唯一入口、唯一闸清单、唯一域划分与选闸算法仍只在 `tools/run_all_gates.sh`,禁在文档另列;触发与追绿语义见「运行与验证」首条与 process-conventions §1)。开发期手动跑批按需选档(`--code` 关联档选测——02 跑「卡片申报 ∪ diff 推导」并集套件、三件保险恒跑、横切面回落全量——算法只在闸脚本内定义),手动跑不构成验收;全量回归由用户自发发起。文档面批与代码面批验收同走合回 CI 全量(`--docs` 仅属开发期手动档,process-conventions §1)
- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;合回后由本地 CI 全量验证(红项修复提交追绿,process-conventions §1)。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,由用户发令走独立 worktree 直改+合回(车道铁律,commit 点零钩子检查);大体量改动(含触及代码面的工程改动如纯测试维护)同车道无特殊通道;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制)
- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;合回后由本地 CI 全量验证(红项修复合回追绿,process-conventions §1)。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,由用户发令走独立 worktree 直改+合回(车道铁律,commit 点零钩子检查);大体量改动(含触及代码面的工程改动如纯测试维护)同车道无特殊通道;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制)
- 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <5 个工作日=「冻结待凑批」、不可开工);流水线闸 `tools/pipeline_gate.py` 强制(已注册进 run_all_gates.sh 文档域);实现段零裁断——卡片没写清=上游缺陷,停该卡退回上游,禁内联求裁(process-conventions §2,格式与状态机=doc-conventions §12)。工程/文档设计/美术素材三类改动不走写卡与编排段:(如需设计讨论)→ 用户发令 → 独立 worktree 直改 → 合回 CI(车道细则=process-conventions §1)
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
+8 -8
View File
@@ -17,7 +17,7 @@
**通用规格纪律**:
- 交付格式 PNG 透明底;动画用序列帧或精灵表(SpriteFrames),帧数规格随样张定(候选档已成文 art-bible §8.4)。命名规范候选草案见 art-bible §8.3。
- **分辨率档位已定案:1280×800 + 1920×1080 两档**,窗口不可调、格边长按档位计算,后续调整走修订;像素画布基准见 **art-bible §8.2**(主角人设原图 48×72〔人设侧画布〕;站桩立绘/战斗状态图源高 48;棋盘小人 32×32 二头身;敌种逐键源档 36–94;整数倍缩放纪律;终值归样张冻结,待办清单已登记)。
- **分辨率档位已定案:1280×800 + 1920×1080 两档**,窗口不可调、格边长按档位计算,后续调整走修订;像素画布基准见 **art-bible §8.2**(棋盘小人 32×32 二头身独立源档;站桩立绘=原稿分辨率入包、显示按三档绝对高 96/144/188 缩放,定值单点与逐件归档见 §8.2 站桩立绘显示档条)。
- 程序化占位是现行结构冻结条款:资产未就位的槽位保持占位渲染,不空窗。
- **复用闸(♻ 渠道四道验收,一道不过不入包)**:① 色板量化——改色对齐 art-bible §4.1 token,不保留原包杂色;② 吸色检查——无 HSV 色相 300°–345°(紫赤禁入带);③ 灰度/色弱可辨——去色相后仍可辨且不与语义件混淆;④ 授权干净——CC0 优先,CC-BY 署名记入设置/关于页,license 不明的一律不用。
@@ -59,14 +59,14 @@
## 2. P1 —— 角色级(样张通过后开工,ComfyUI)
> 提示词规格卡 art-prompts.md 现行为 **v2.0**(主角三态+八敌种,逐资产自包含完整提示词,整段可直贴)。
> 提示词规格卡 art-prompts.md 现行为 **v2.13**(主角三态+八敌种逐卡提示词仍在册;站桩立绘走 §5.4 按档派生、不重画)。
### 2.1 主角战斗小人
- **要什么**:待机 / 攻击 / 受击 三态起步(移动态样张后定);透明底 PNG。
- **风格/规格**:小人原型——**窄、直立、对称,轮廓凹凸 ≤2**;2~3 头身两档下剪影均成立;主色小人蓝 `#2E86DE`(候选值,样张冻结终稿);格内占位最窄、不吞数字位。
- **来源**:✦ ComfyUI(prompt 卡 v2.0 §2);剪影原型是设计载荷(宽≥高/凹凸计数/甲档/紫赤独占),素材站无对的,不复用。
- **现状**:站姿样张已有先例(源文件已清理);战斗三态未出。
- **现状**:战斗三态在役(迭代 6 T3 落位);2026-11-01 显示档改中档 144,按档派生落位归立绘显示档实装批。
### 2.2 八敌种形象(键集事实源 = `data/tables/enemy.tres` 行键)
@@ -81,7 +81,7 @@
| 钩爪恐魔 hook_horror | 普通怪(钩爪/多足轮廓注意凹凸计数控制) | light | 第三关群行捕食者,小体量可辨 |
| 灰矮人石君王 duergar_king | **Boss:锐角与不对称**,体量最大 | heavy | 第三关 Boss;紫赤色相独占+形状独占双通道 |
- **通用规格**:亮色描边+落影底盘轮廓通道硬规则;透明底 PNG;每敌种 2–3 版候选;护甲类型视觉区分方案已落 art-bible §5.3(已定稿)。
- **通用规格**:亮色描边+落影底盘轮廓通道硬规则;透明底 PNG;显示档三档绝对高 96/144/188、源=原稿不降采样(art-bible §8.2 v3.32);护甲类型视觉区分方案已落 art-bible §5.3(已定稿)。
- **来源**:✦ ComfyUI(prompt 卡 v2.0 §3,八敌种+L2 熊地精变体共 9 卡);理由同 §2.1——不复用。
- **联动**:显示名以 `data/tables/enemy.tres` 与敌人系统文档为准(L2 熊地精=bugbear 复用,显示名熊地精系)。
@@ -132,8 +132,8 @@
- **要什么**:营地场景图 10 张——`scene_camp_bg`(背景层,不透明全幅=天空+远山+整片地面+门洞内一条通向远处的小路)+ `scene_camp_wall`(围墙层,透明底=横贯全宽密排木桩墙+两根门柱,门洞留透明缺口)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件)。钥石顶栏图标沿用现役 `icon_keystone`。
- **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#F6C667` + 木色 `#8A5A34`(art-bible §6.2);2× 源档出图、最近邻降采样到 1280×800;后处理=抠底/降采样/色板量化/描边统一。**出图登记照 [remote-gen-prompts](remote-gen-prompts.md) 头注轻量口径执行:用途+生成时间必注,其余参数有则记。**
- **排期**:营地交互重做**美术自立批**(不占迭代位;UI 贴图重构批已搁置)。终稿落 `game/assets/art/scene/`,消费常量 `UiKit.TEX_CAMP_*`。
- **现状**:全占位。
- **排期**:已执行(随迭代 47 卡④,2026-11-01 合回)。终稿落 `game/assets/art/scene/`,消费常量 `UiKit.TEX_CAMP_*`。
- **现状**:已入包(2026-11-01 迭代 47 卡④——十件按锁定矩形 contain 落位、营地主色板 32 色重量化;用户游戏内终审如有置换意见,按同径替换)。
### 3.6 字体(全局 UI 像素字,已定稿单套)
@@ -162,7 +162,7 @@
| 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint;连续地面层在其下方,先保留 | ⚙/✦ 自产 |
| 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | ✦/⚙ 自产(同 §2.3) |
| 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | ♻ 粒子/碎块改色 + ⚙ 坍塌结构自产 |
| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记旗面走封印黑 `#000000`(现行封印色,色史归 art-bible §4.1);特效走运动+色相(§2:状态/情绪不走明度,空间光照可走);现行占位 = 封印黑「旗」字形;印记常显形态可与 §3.3 旗面像素化合并出 | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) |
| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记旗面走封印黑 `#000000`(现行封印色,色史归 art-bible §4.1);特效走运动+色相(现行取法——明度分层不动);现行占位 = 封印黑「旗」字形;印记常显形态可与 §3.3 旗面像素化合并出 | ✦/⚙ 自产(精灵+粒子,烟尘光点可 ♻ 改色) |
| 弹道/退场等动画密度 | 战斗表现输入清单,方案未定 | 样张锁动画方案后启动 | ♻ 通用弹道/光点/消散粒子改色为主 |
**现状**:三关舞台背景层与连续地面层已交付(2026-10-31 入包 `game/assets/art/scene/`:dungeon v10c / underdark v10c / deep_underdark v10c,均 1280×252;共用地面 v12b,1280×100,按作者裁保留原亮度 L≈0.132);小石台件已在役。
@@ -187,7 +187,7 @@
└─> 敌种呈现形态裁决 ✅已完成(全站桩,卡片式已弃)
徽章(3.1)/节点图(3.2)/效果图标(3.4) —— 独立线,可随时插队
♻ 复用件(3.2/3.4/3.5/3.6/演出粒子) —— 不依赖样张,可随时插队(入包闸 = §0 复用四道)
字体(3.6)接入归表现批;营地场景图(3.5)归营地交互重做美术自立批
字体(3.6)接入归表现批;营地场景图(3.5)已随迭代 47 卡④ 入包(2026-11-01)
分辨率档位 ✅已定案(1280×800 + 1920×1080,2026-09-22 用户确认);art-bible §8 资产规范 ✅已成文(2026-09-23)——像素基准/画布/命名/帧数候选值就位,终值归样张批冻结
```
+20 -20
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.30,历次修订明细见 git 提交史
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.35,历次修订明细见 git 提交史
>
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -30,7 +30,7 @@
**原则二|明亮为皮,明度分档为骨**(取自 Moonlighter、Peglin;锚定 P1 可读性前提、P4 探险氛围)
取 Moonlighter 的暖质地砖与亲和冒险者气质、Peglin 的明快全年龄洞穴色彩:全局暖倾向、无压抑。可读性由**格底明度分档**承载——已开格(数字所在格)明度走世界底层档(§2 明度分层;格态语义见《排雷玩法》;美术侧据此未开格走受光墙顶档、已开格走挖出空间沉暗档,与 §8.5 图底分离同轴),亮族数字墨直接画在格底上,直画对比 ≥4.5:1 恒成立。**数字色牌为后备手段**——仅复杂格底(物件纹样/变异纹带/微光叠加致格底对比不足)时垫牌后备(画法 §3.3、色值 §4.1)。格子本体美术放开:纹理、材质、装饰变体、类型纹样皆可入画;格底参数域见 §4.1 硬指标(分档明度域、饱和上限),仍避 Boss 禁入色相带。与参考之别:ITB 以半透明压暗板承载信息;本作格底功能分档(信息可读性服务,属 §2 明度分层允许域),情绪压暗仍禁。验收:机检〈1–8 全档数字对所在已开格底对比 ≥4.5:1〉
取 Moonlighter 的暖质地砖与亲和冒险者气质、Peglin 的明快全年龄洞穴色彩:全局暖倾向、无压抑。可读性由**格底明度分档**承载——已开格(数字所在格)明度走世界底层档(§2 明度分层;格态语义见《排雷玩法》;美术侧据此未开格走受光墙顶档、已开格走挖出空间沉暗档,与 §8.5 图底分离同轴),亮族数字墨直接画在格底上,直画对比 ≥4.5:1 恒成立。**数字色牌为后备手段**——仅复杂格底(物件纹样/变异纹带/微光叠加致格底对比不足)时垫牌后备(画法 §3.3、色值 §4.1)。格子本体美术放开:纹理、材质、装饰变体、类型纹样皆可入画;格底参数域见 §4.1 硬指标(分档明度域、饱和上限),仍避 Boss 禁入色相带。与参考之别:ITB 以半透明压暗板承载信息;本作格底功能分档(信息可读性服务)。验收:机检〈1–8 全档数字对所在已开格底对比 ≥4.5:1〉
*设计测试:当格子美术与数字可读性冲突时,先调格底明度档,复杂格底垫色牌后备,不动格子画。*
@@ -69,7 +69,7 @@
> 本节服从第 1 节宣言「明快的地牢,轻快的扫荡,仔细的推理」。能量值为全作统一 0–10 刻度:0 静谧,10 沸腾。
>
> **明度分层(硬规则)**:明度按视觉层分带——**信息面层**(数字/UI 数据/预告/语义标识)最高、**世界中层**(格底顶面/角色/物件)居中、**世界底层**(背景/纵深/远景/已开空间)可更低;层间明度差服务空间纵深与光照(Moonlighter 式洞穴层次)。**情绪明暗禁令**:明度变化禁止用于**情绪表达**——画面禁止靠压暗/渐晕阴沉下来;roguelike 失败重开,世界无光暗记忆;Darkest Dungeon 式压暗仍在 §9.2 反面清单。**状态表达不受本禁令约束**:状态可用明度、色相、饱和、运动、元素任一手段承载。**允许域**:空间纵深、光照氛围、形体塑造。类型微光的明度梯度(§4.2)属信息面层内梯度,不扰世界层明度档。验收:目检〈情绪表达不走压暗/渐晕;三档明度带层间灰度可分〉
> **明度分层(硬规则)**:明度按视觉层分带——**信息面层**(数字/UI 数据/预告/语义标识)最高、**世界中层**(格底顶面/角色/物件)居中、**世界底层**(背景/纵深/远景/已开空间)可更低;层间明度差服务空间纵深与光照(Moonlighter 式洞穴层次)。类型微光的明度梯度(§4.2)属信息面层内梯度,不扰世界层明度档。验收:目检〈三档明度带层间灰度可分〉
>
> **世界底层两子带**:世界底层拆两条子带——**底层下沿带 L 0.014–0.06**(远景、洞窟纵深、B 区舞台底、屏幕根底)与**底层上沿带 L 0.08–0.14**(已开格底、迷雾记忆态合成底),子带间隔 ≥0.02。拆两子带用于解决**世界底层内部** B 区舞台底(L=0.036)与 D 区已开格底(L≈0.10)同层难辨的问题——两者同属世界底层,单带下跨度 0.126 吃掉层间间隔 0.08,灰度图上会表现为两个不同的底。§1.4 整屏②项约束的是层间可分(底层上沿 0.14 对世界中层下沿 0.22,差 0.08 本就满足),②项不约束同层内可分,故本条不属②项的成立要件,而是世界底层内部的可读性纪律。验收:机检〈同一屏的世界底层成员全部落进两条子带之一,两子带 L 区间不重叠且间隔 ≥0.02〉
@@ -84,15 +84,15 @@
### 2.2 轮末自动战斗结算
- **主情绪**:延迟兑现的清算爽感——扫雷之因逐行兑现为战果;完美封印(概念定义与兑付结构见《地图战斗资源》)的高光兑现——结算带高光行,以其为全场喝彩级的最高反馈档
- **色彩性格**:情绪不走明度(§2 禁令),色彩转向金橙主导、饱和拉高;数字色转译为弹道色沿行推进
- **色彩性格**:情绪由色相与饱和承载——金橙主导、饱和拉高,明度分层不动;数字色转译为弹道色沿行推进
- **氛围形容词**:酣畅、递进、一气呵成
- **能量**:7/10,随连击爬升
- **承载元素**:兑现光带逐行横扫棋盘,开格化箭雨、印记化暴击砸向敌人,因果全程可见可归因。验收:目检〈兑现光带逐行横扫、因果可见可归因;情绪表达不走压暗〉
- **承载元素**:兑现光带逐行横扫棋盘,开格化箭雨、印记化暴击砸向敌人,因果全程可见可归因。验收:目检〈兑现光带逐行横扫、因果可见可归因〉
### 2.3 清图奖励与换图
- **主情绪**:短促丰收——宝箱揭盖式的喜悦,三五秒即收,不拖节奏
- **色彩性格**:全屏饱和峰值:金粒喷泉与彩纸迸发,饱和瞬时拉满后回落;明度不动(§2 情绪禁令)
- **色彩性格**:全屏饱和峰值:金粒喷泉与彩纸迸发,饱和瞬时拉满后回落;明度分层不动
- **氛围形容词**:丰收、轻快、干脆
- **能量**:8/10,尖峰后速降
- **承载元素**:最后一格碎砖中喷出的 GP 喷泉,叮当落入 UI 钱袋。验收:目检〈金粒喷泉短促收敛,饱和峰值后回落〉
@@ -108,7 +108,7 @@
### 2.5 战斗失败(冒险终结)
- **主情绪**:温柔的惋惜与「再来一次」的邀请——有分量、不惩罚,像合上一册冒险日志
- **色彩性格**:情绪不走明度(§2 禁令),色相转暖灰掺暮蓝;全作禁血红与黑场,唯此一拍最静
- **色彩性格**:情绪由色相承载——色相转暖灰掺暮蓝,明度分层不动;全作禁血红与黑场,唯此一拍最静
- **氛围形容词**:惋惜、安静、余温、可再来
- **能量**:1/10
- **承载元素**:小人坐在碎砖上摇头掸灰,落下的封印印记逐枚化作光点收回行囊。验收:目检〈无血红与黑场;色相转暖灰掺暮蓝〉
@@ -159,7 +159,7 @@
### 3.2 环境几何
- **雷图棋盘呈现基准**:战棋地块质感 × 正俯视角——取 ITB 参考图(`references/itb_1.jpg`)的地表质感骨架:地块纹理多样、起伏体块感、岛缘/砖缘厚度、格上装饰小立牌;但相机一律正上方垂直向下,格线屏轴对齐,**禁等距/斜视角与菱形格**;形状语言仍守总纲圆角方形。砖缘厚度与岛缘立体面属形体塑造,不属状态/情绪表达,不受 §2 情绪明暗禁令约束(属 §2 明度分层允许域;顶面格底仍守 §4.1 参数域)。装饰小立牌属 ITB 参考的观感元素,本作不采用;装饰变体走氛围层纪律(纹理级/物件级形状家族,见下方装饰变体条),零信息纪律以原则三为准。验收:目检〈棋盘正俯视、格线屏轴对齐、无菱形格〉 ←原二;原三;§2
- **雷图棋盘呈现基准**:战棋地块质感 × 正俯视角——取 ITB 参考图(`references/itb_1.jpg`)的地表质感骨架:地块纹理多样、起伏体块感、岛缘/砖缘厚度、格上装饰小立牌;但相机一律正上方垂直向下,格线屏轴对齐,**禁等距/斜视角与菱形格**;形状语言仍守总纲圆角方形。砖缘厚度与岛缘立体面属形体塑造(顶面格底仍守 §4.1 参数域)。装饰小立牌属 ITB 参考的观感元素,本作不采用;装饰变体走氛围层纪律(纹理级/物件级形状家族,见下方装饰变体条),零信息纪律以原则三为准。验收:目检〈棋盘正俯视、格线屏轴对齐、无菱形格〉 ←原二;原三;§2
- **地砖**:一切环境体由同一圆角方形衍生:未开 = 完整凸砖,已开 = 同形缺角碎裂,雷 = 碎裂加巢穴纹(危险格玩家说法见《排雷玩法》);只以"完整度"叙事,不为状态新造形。验收:目检〈三格态仅以完整度区分,无新造状态形〉 ←原三;P4
- **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三
- **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3
@@ -172,7 +172,7 @@
- **高墙瓦片体系|光照纪律(单一北受光)**:北缘 = 墨线内侧 1px 微亮棱(就地取色提亮,禁宽亮带);西/东缘 = 墨线后直进纹理、不加修饰;南缘 = 墨线 + 立面窄暗条(约 1/3 格高,横层理叠石、禁竖条纹栅栏感)。验收:目检〈北缘亮棱、南缘窄暗条;无宽亮带、无竖条纹栅栏感〉
- **高墙瓦片体系|凹角收头**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出。验收:目检〈凹角收头与转角竖线同列,无悬空突出〉
- **高墙瓦片体系|现行定稿**:`game/assets/art/board/terrain_*`(16 唯一墙瓦片 + 5 地面瓦片变体;词表与查表逻辑落码于 `board_world.gd`)。验收:机检〈terrain_* 素材 16 墙瓦片 + 5 地面变体在库,查表逻辑与词表一致〉
- **高墙瓦片体系|高墙感 D 投影档与整板悬崖**:南临空墙格 = 立面口沿高光 2px `Color8(196,172,132)` + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起);整板悬崖 = 板底/右侧 `DEPTH_PX 8px` 暗面 + 列分隔竖缝 + 岩层横纹 + 口沿高光 + 接触 AO + 底线收边 + 悬浮影(游戏 `board_world.gd` 同款结构件,样张脚本与游戏渲染常量同源)。验收:目检〈南临空墙格呈口沿高光+双段暗带;北邻墙地面格呈三段渐隐投影;整板悬崖结构件齐备〉
- **高墙瓦片体系|高墙感 D 投影档与整板悬崖**:南临空墙格 = 立面口沿高光 2px `#C9AF83`(201,175,131——代码色 `BRICK`,与掩尘枚同源单点) + 双段暗带(0.16/0.25 格边,黑罩 α0.60/0.82);北邻墙地面格 = 三段渐隐投影(顶带 0.22 格边,α0.28 起);整板悬崖 = 板底/右侧 `DEPTH_PX 8px` 暗面 + 列分隔竖缝 + 岩层横纹 + 口沿高光 + 接触 AO + 底线收边 + 悬浮影(游戏 `board_world.gd` 同款结构件,样张脚本与游戏渲染常量同源)。验收:目检〈南临空墙格呈口沿高光+双段暗带;北邻墙地面格呈三段渐隐投影;整板悬崖结构件齐备〉
- **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/旗);原三
### 3.3 UI 形状语法(方向指导,细则见 §7.1)
@@ -209,10 +209,11 @@
- **图标槽柔和档 `#806A4E`(SLOT_BACKDROP_SOFT)**|BCard 图标槽底:对米色卡面 PANEL_DATA 3.69:1(深档 SLOT_BACKDROP #3E362C 8.53:1 过强、像硬贴补丁,故槽底另立柔和档),槽内白图标 5.14:1,满足 ≥3:1 图形参考线硬约束;仅 BCard 图标槽换档,B 区状态槽仍用深档 SLOT_BACKDROP。验收:机检〈槽内白图标对槽底 ≥3:1〉
- **群伤微光暗档 `#A61D37`(TG_GLOW_AOE)**|类型微光群伤组的**明度档**(UI token 板,占 1 枚额度,**余量 3→2**):色相 349° 与亮档 `#FF2D55` 零移动,明度落 V=0.65 档——即 §4.2「微光 = 色相+明度双码」里明度侧的落法;与 §4.2「红只说危险」不相撞(微光组色相刻意错开语义红,见 §4.2「类型微光色带」条)。已在役(`ui_kit.gd` 常量与 `type_glow_test.gd` 守门断言),本节为登记转正。验收:机检〈ui_kit.gd TG_GLOW_AOE 与 type_glow_test 断言的 H/S 逐位一致、V 落 0.65 档〉
- **站桩石板地台板身 `#968264`(STAND_STONE)**|战斗屏场景环境子板(分层②,不占 48 token 额度):B 区站桩地台板身与顶缘的材质色,跨三关恒定、不吃 §4.3 `wall_ambient` tint(地台是 §6.1 定死的世界中层承载面,随关卡变会破 §2 明度分层的层间关系)。转正前即在役。验收:机检〈战斗屏场景环境子板色数 ≤32 且本值在册〉
- **营地场景环境子板(分层②,不占 48 token 额度)**:营地场景十层环境色按子板制落地——各层独立量化并集 272 色经最近对合并收敛至 **32 色、违例对 0**,十层按终版色板重量化回写(2026-11-01 迭代 47 卡④)。色值不另立逐值清单,事实源=入包素材 `scene_camp_*` 十件的量化产物;换算与合并过程登记于 artwork 工作区换算式登记(git 提交 3443890d 一线可考)。验收:机检〈营地屏唯一色数判据(shot_checker 闸件承载,2026-11-01 用户裁口径)〉
- **重甲冷灰金属材质色 `#868C9A`(ARMOR_PLATE_COLD,token 名暂拟、随实装批登记)**|语义/UI token 板(**分层①**,占 1 枚额度,**余量 2→1**;用户裁):重甲档甲材质色;character-design §5.3 通道 = 甲面覆盖率 × 甲材质色,甲材质色按甲档统一,故石君王岩石板甲**同引本枚、不另计额度**;材质色跨屏恒定,落分层①不落分层②(环境色才随场景走)。过门情况:第二道门按宽松口径(禁止三通道全 <8)与在册色逐一比对,**无三通道全 <8 的对 → 过门**;§4.2 语义检查过门(色相 222°,不占红/金/蓝/紫赤/绿语义段、不在 300°–345° 禁入带;材质色不进信息层与语义物件岗)。对比实测:对深素底 `#282220` 4.65:1、对舞台底 `#3E362C` 3.52:1(图形 ≥3:1 门槛);相对亮度 L≈0.261 落 §2 世界中层 0.22–0.45 带。验收:机检〈本值入板后全板色对中三通道差全部 <8/255 的对数 = 0〉
- **卓尔浅紫灰皮 `#A79CB8`(DRIDER_SKIN,token 名暂拟、随实装批登记)**|语义/UI token 板(**分层①**,占 1 枚额度,**余量 1→0,额度用尽**;用户裁):蛛化卓尔精灵上体皮色(体块主色,L≈0.355 落 §2 世界中层带)。过门情况:第二道门同上行口径,**无三通道全 <8 的对 → 过门**;§4.2 语义检查过门(色相 264°,避开 300°–345° 禁入带,紫赤独占仍归 `#8F2F6A`;不占本节五语义段)。对比实测:对深素底 `#282220` **6.04:1**、对舞台底 `#3E362C` **4.57:1**——**两处均过图形 ≥3:1 门槛**(本轮新算)。验收:机检〈本值入板后全板色对中三通道差全部 <8/255 的对数 = 0〉
- **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#A8AEB8`)→ 并入 `FOG_MEMORY_INK #C4C4C4`,**部件取值 = `#C4C4C4`**;② 主角提灯铜色(原拟 `#C08A3A`)→ 并入 `GOLD_DEEP #B07A14`,**部件取值 = `#B07A14`**;③ 食人魔酋长暖褐皮(原拟 `#B08050` / `#A97C4E`)→ 并入 `暖石 #B8A582`,**部件取值 = `#B8A582`**;④ 石君王灰皮(原拟 `#9A968C` / `#949089`,按**次级皮**口径——板甲视同体块主色、灰皮属次级皮故不受 L 0.22–0.45 约束)→ 并入 `STONE #786E62`,**部件取值 = `#786E62`**;⑤ 轻甲护甲图标木色 `#8A5A34`(深素底下 2.72:1 不过门;原拟新增木色亮档 `#C49A5E`)→ 并入 `WARM_FAINT #CDB88C`,**部件取值 = `#CDB88C`**(8.08:1)。**一色两岗代价已随裁定接受**:① 的 `#C4C4C4` 语义岗 = 迷雾记忆墨(§6.5)、② 的 `#B07A14` 属金族(§4.2 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉
- **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①`#C4AC84`(地台顶缘受光线,代码 `STAND_STONE_RIM`)→ 并入 `#C9AF83`(掩尘,并与 §8.2 高墙口沿高光 `Color8(196,172,132)` 统一为同一枚);②`#5A4C3A`(地台描边,代码 `STAND_STONE_EDGE`)→ 并入 `#4C4038`;③`#3E342A`(舞台底,代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)。①②的绘制点全部改指合并目标色、源常量删除——程序化回退绘制与已落合并值的贴图保持一致;③随 §6.1 舞台背景插画批一并处置:背景资产接管该绘制面时本常量随绘制点退役或改指,禁单独为改值出批。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉
- **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①地台顶缘受光线 `#C4AC84` 已并入 `#C9AF83`(掩尘枚 `BRICK`,与 §8.2 高墙口沿高光同枚)——源常量已删除(全仓零引用,退役断言测试串为预期例外),绘制点全部改指 `#C9AF83`;②地台描边 `#5A4C3A` 已并入 `#4C4038`(代码 `PLAZA_EDGE`,ui_kit 唯一落点)——源常量已删除,绘制点全部改指;③舞台底 `#3E342A`(代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)——随 §6.1 舞台背景插画批一并处置:背景资产接管该绘制面时本常量随绘制点退役或改指,禁单独为改值出批。程序化回退绘制与已落合并值的贴图保持一致。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉
**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9
@@ -377,7 +378,7 @@
- **双舞台常驻下的 B 区环境**:战斗画面区(站桩立绘所在)的背景 = 明亮地窟,与 §1 风格基调同源——暖倾向、无压抑;背景整体**低对比自然后退**(§3.4 焦点预算延伸):背景禁止构成完整凸形、禁止抢 B 区立绘与 D 区数字的焦点,识别层级恒为 立绘 > 背景。验收:目检〈背景无完整凸形、立绘最抢眼〉
- **舞台正面目标**:B 区 = **地窟看台**——立绘脚下地台/落影承载(世界中层),背景环境层低饱和后退、明度走世界底层档;角色禁止直接贴平色底。规格与终值归整屏样张闸(§1.4)冻结。**地台面上零文字**——暖白 `#F0E2C8` 对地台板身 `#968264` 实测仅 2.89:1,已越 §7.2 的 4.5:1 硬指标,落法按 §7.2「文字禁直接压纹理底」(垫容器、素牌或罩层),非新条款;站桩文字位改垫素底或落进结算明细带(实装=站桩名字行垫 `HP_BAR_BACK` 深档素底牌、文字基线上移至地台顶上方——2026-10-30 落地)。验收:目检〈立绘脚下有地台/落影,非贴平色底〉+机检〈B 区站桩文字位正下方像素非地台板身色 #968264〉
- **明度分层适用**:背景氛围的情绪表达仍禁止以压暗/渐晕实现(§2 禁令);空间纵深与洞穴感走明度分层允许域——世界底层档、光照氛围、色相饱和与形体承载(§3.2 同款逻辑)。验收:方向性
- **明度分层适用**:空间纵深与洞穴感走明度分层——世界底层档、光照氛围、色相饱和与形体承载(§3.2 同款逻辑)。验收:方向性
- **图类型联动**:背景层按 `board_type` 选三张变体(`scene_stage_bg_dungeon.png` / `scene_stage_bg_underdark.png` / `scene_stage_bg_deep_underdark.png`,方向取 §6.3 三关色板候选);**背景不再走 §4.3 `wall_ambient` tint**(该槽保留给棋盘/世界的墙雾氛围,避免对三张背景二次染色)。紫赤仅 Boss 战允许短暂进入背景氛围纹样(§2.6 色相独占的舞台侧延伸,高饱和独占不变),非 Boss 战高饱和紫赤禁入。验收:机检〈背景吸色无非 Boss 高饱和紫赤;背景素材不引用 `wall_ambient` 槽〉
- **场景构成(2026-10-31 用户裁)**:B 区舞台从远到近分三段——**顶部空区**:洞顶以上的昏暗空间,只留深色空区,**不画钟乳石、吊锥、悬挂物或任何可辨形状**;**远景(纵深洞厅)**:不是贴墙的后壁,按远/中/近三层组织——远层暗剪影(远处洞口/通道、层叠岩体、洞底轮廓)、中层主要细节层(岩台、岩柱、凹壁、裂缝)、近层左右暗色岩框;剪影被中层部分遮挡、不贴顶、不孤立,**禁贴墙感、禁单件圆柱贴顶、禁砖石墙/规则砌缝/方整石块**;三关母题(地精=支洞/木栈道/木桩围栏;卓尔=巨柱/石笋/蛛网/螺旋塔剪影/巨菇/石桥;灰矮人=石笋城郭/棱角石厅/石桥/矿道口/烟囱塔)见 `battle-stage-prompts.md` §3.2;**分隔带**:远壁根部与近景地面之间一条不规则碎石带/阴影缝,边界清楚但不笔直,**禁瓷砖缝、踢脚线、规则台阶**;**近景地面**:干净压实泥土为主,全幅只零星几颗小石、散点分布,中央平缓留空给角色站位,**禁成排/成堆碎石、满铺砂砾、泥点噪声、瓷砖、规则铺装、草、木地板**。纵深与三段构图归 §1.4 样张;逐条要素、分层提示词与否定后缀 = `battle-stage-prompts.md`(本文件不重复维护)。验收:目检〈顶部无钟乳石/吊锥;远景有纵深、非贴墙;无孤立圆柱贴顶;近景为泥土碎石;分隔带不规则;中央无大障碍〉
- **图层归属(2026-10-31 用户裁)**:背景层 = 三关变体 `scene_stage_bg_dungeon/_underdark/_deep_underdark.png`(顶部空区+纵深洞厅远景,不画近景地面;按 `board_type` 选一张);近景地面 = **独立连续地面层** `scene_stage_ground.png`(泥土/砂砾/碎石地面,全宽、底边贴 B 区底、顶缘即分隔带;高度读布局表 `ground_h`,字段由地面层实装批新增并回填,本批终稿高 100、代码批落表);现有每角色小石台 `scene_stage_plaza.png` 保留,叠在连续地面之上。连续地面层与小石台**三关共用**、跨关恒定、**不吃 `wall_ambient` tint**(世界中层承载面随关卡换明度会破 §2 层间关系);背景三张变体各自带关卡色族,不再叠 tint。验收:机检〈三张背景文件名带 board_type 后缀;地面层/小石台文件名无 board_type 后缀;背景与地面层均不引用 `wall_ambient` 槽〉
@@ -390,12 +391,12 @@
- **情绪与色域**:承载 §2.8——全作最暖:烛光黄 `#F6C667` × 木色 `#8A5A34`(§7.2),中等饱和,无棋盘式高对比。
- **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=**独立成层的木围墙**(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**;近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙,唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。
- **图层拆分(10 张)**:场景按「远景/中景/近景要素各自成层」拆——`scene_camp_bg` 背景层(z0,不透明全幅)=天空+远山+整片地面+门洞内一条通向远处的小路;`scene_camp_wall` 围墙层(z1,透明底)=横贯全宽密排木桩墙(左右两段)+两根略高的门柱,**门洞处留透明缺口**透出背景层的小路;`scene_camp_board` 布告板(z2)、`scene_camp_gate` 门扇(z3)、`scene_camp_dummy` 木桩/`scene_camp_forge` 锻炉/`scene_camp_tent` 帐篷(z4)、`scene_camp_chest` 宝箱/`scene_camp_notebook` 笔记本(z5)、`scene_camp_campfire` 篝火(z6,最前)=八个物件层,各透明底单件一张。**帐篷**为承载件、不可点。围墙独立成层的好处:桩高可单独锁死、门洞做透明缺口、背景层不混入暖木色。提示词与坐标 = `camp-scene-prompts.md`,消费常量 = `asset-path-map`。
- **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条 `(0, 240, 1280, 70)` 裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB)。
- **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB);**实装落盘矩形 `(0, 261, 1280, 49)`、底锚 y310**(Y 压缩按桩身 43px 终稿锁定值精确,落带内〔规划带 (0,240,1280,70)〕;备选 A′〔带高 70/桩身 62〕存档 artwork 同目录——2026-11-01 迭代 47 卡④ 实装)。
- **色彩与氛围依据**:营地场景的冷暖分工(冷的大面积后退、暖的小面积前进)、空气透视三档与大面积用色规则 = `reports/提案-营地色彩与氛围脚本-2026-10-23.md`。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 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 换挡方向)
@@ -422,7 +423,6 @@
- **记忆态可读性判据**:数字对合成底**归一后(真实渲染面)三关 ≥4.5:1**(WCAG 硬指标;合成底 L1 #4C4038/L2 #3F4147/L3 #3E4048;行值与素材随动,测点=ui_contrast_probe 迷雾段)。**归一注**:装载亮度归一是现行生产路径(`board_view._normalize_floor_luma`),原图均值≠渲染面,复算须落归一后底。可辨性自查(三通道,随样张/实机自检):①对全黑未开格——合成底 #3E3731(L≈0.036)对 L0,实机目检常光档可分(暗房档一并抽检);②对灯光区内活态已开格——主判据=数字墨灰度差(记忆墨 L0.55 对亮墨三档 L≥0.75 / 色牌 #F5EAD3 L≈0.83,灰度 ΔL≥0.20)+ 色相通道(记忆态近中性灰,对活态暖底暖墨);③格态三形在记忆态保持灰度可辨——形通道经纹理+灰罩形态不变,沿 §1.4 验收 2 同源判据。验收:机检〈ui_contrast_probe 迷雾段记忆墨对比 ≥4.5:1〉←§1.4
- **迷雾过渡边:硬边(逐格二值)**:灯光区边界 = **整格二值切换**(区内全渲染 / 区外全黑或记忆态),**不做渐变过渡带、不做半透明晕边**。理由:①边界是信息边界不是氛围晕——渐变中间态 =「看得见但看不清」,破数字可读硬指标与记忆态明度档;②现行事实源验收断言(边界格呈态、两态逐位一致)须格级二值可判;③与砖面整格表达(§3.2 高墙瓦片体系)、ITB 格内信息分层气质同轴。无动画,减动效无涉。←现行事实源;§1.2 原则一;§3.2
- **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉
- **§2 明度禁令自查**:迷雾遮蔽与记忆态灰显属 §2 明度分层**光照氛围允许域**(洞穴举灯的空间光照呈现,与 Moonlighter 取列「亮地板/暗深渊/发光体」光照层次同族),非情绪压暗(§9.2 Darkest 反面条款不触——那禁的是情绪表达)、非状态明暗编码(格态语义仍由形+色相承载,灰度下三态可辨不受扰)。←§2;§9.1;§9.2
---
@@ -516,19 +516,19 @@
- **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉
- **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×、**护甲类型图标三枚 24px=1×**——源 24×24,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉
- **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉
- **站桩立绘槽位=高定宽随**:立绘/精灵槽位只定「高度的整数倍档」,宽度随源图宽高比自适应——**禁止把全部立绘塞进统一方框强拉**(纵横倍率必须恒等)。反面实例:统一方框把窄条生物源(细长本体裁掉透明边)按主角比例横拉压扁发花。乙类的非整数豁免只覆盖**等比**缩放,不含纵横异倍变形。验收:机检〈立绘纵横倍率恒等〉
- **站桩立绘槽位=高定宽随**:立绘/精灵槽位只定「高度的绝对档(档高取整数像素值)」,宽度随源图宽高比自适应——**禁止把全部立绘塞进统一方框强拉**(纵横倍率必须恒等)。反面实例:统一方框把窄条生物源(细长本体裁掉透明边)按主角比例横拉压扁发花。乙类的非整数豁免只覆盖**等比**缩放,不含纵横异倍变形。验收:机检〈立绘纵横倍率恒等〉
- **1920×1080 档策略**:全局等比(stretch=canvas_items+aspect **keep**)——16:10 设计在 16:9 窗口=等比 1.35×+左右黑边各 ~96px,构图与 1280×800 基准档完全一致;甲类 1.35 非整数缩放例外注记:轻度宽窄不均系本档位接受代价(像素游戏 shipped 常态),实机目检可感不适→回乙案(1920 独立 2× 基准)重议;乙类同前款。验收:目检〈1920 档构图与 1280 档一致〉
- **画布基准(暂定值)**:
- **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致);
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销;
- **站桩立绘源档 = 八敌逐键定档**:主角 48 不变;八敌源档高 = goblin 36 / giant_spider 38 / duergar 46 / hook_horror 58 / bugbear 62 / ogre_chief 74 / drider 90 / duergar_king 94,显示一律 **×2 整数档(NEAREST)** = 72–188。定档依据 = 生物体型三约束(钩爪恐魔>灰矮人、灰矮人<熊地精、食人魔>熊地精——体量=物种体型,非强度线性映射,见 character-design §5.2);可读性实测门槛 = 源高 ≥32(24 糊/32 可读/48 清楚/64 细节完整),全阶梯 36–94 达标;派生管线 = pixel-1.png 紧裁 → 等比缩至档高 → dom_block_resize 块内众数降采样(tools/pixelize_candidates.py `STANDEE_TIER_H`,`SIZES` 降格为管线上游中间分辨率表);体量存在感经整屏对比校验(B 区高读 `stage_h`/立绘显示上限 188)。验收:机检〈standee_asset_check 逐键档高校验〉
- **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:drider/duergar_king)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
- **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉
- **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
- **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉
### 8.3 导出格式与命名规范
- **归档纪律**:图片素材**不入 git 版本管理**——仓库只保留运行时引用资产(game/assets);生成链路原始产物(AI 原图/中间产物/候选版本)归档至 `archive/` 并择机上对象存储备份;主文档(圣经/GDD/清单)**禁引用 artwork/ 散文件路径**——定稿成图以 game/assets 为唯一事实源,过程稿引用一律用归档包路径或描述性出处。验收:机检〈主文档 grep 无 artwork/ 散文件路径〉
- **入包与对账检查器(挂点已立,实现随 PRD 工具批)**:①**色板检查器**——扫描新素材色板,报新增色/近色告警/超 §4.1 上限(48)拦截,挂素材流入包步(asset-path-map §2 第 3 步);②**路径核对器**——扫描 asset-path-map 声明的全部路径,悬空/未清即报,随大批与审计例行跑;③**standee_asset_check 硬闸升级规格**——按 §8.2 逐键定档校验体量与档高、像素域色数 ≤32、无非整数倍缩放痕迹、描边亮度达标(亮描边明度档);把「高定宽随」教训变成代码闸而非文字。验收:机检〈三检查器在闸可跑〉
- **入包与对账检查器(挂点已立,实现随 PRD 工具批)**:①**色板检查器**——扫描新素材色板,报新增色/近色告警/超 §4.1 上限(48)拦截,挂素材流入包步(asset-path-map §2 第 3 步);②**路径核对器**——扫描 asset-path-map 声明的全部路径,悬空/未清即报,随大批与审计例行跑;③**standee_asset_check 硬闸升级规格**——按 §8.2 显示档逐件校验显示档高与归档、纵横倍率恒等、像素域色数 ≤32、描边亮度达标(亮描边明度档);把「高定宽随」教训变成代码闸而非文字。验收:机检〈三检查器在闸可跑〉
- **交付格式**:PNG 透明底;动画 = 序列帧或引擎精灵表(SpriteFrames),帧尺寸恒定、横向等宽排列(候选)。验收:目检〈交付件透明底无杂边〉
- **命名规范(候选草案)**:`<域>_<对象键>_<状态|变体>[_<序号>].png`,全小写 snake_case;**对象键 = 数据表主键**(effect_id/reward_id/cell_type/enemy_id——图标键契约 §4.6 的文件侧延伸:资产文件名键 = 图标键,全局恒定禁运行时换图);图类型变体后缀 = board_type 键(`_dungeon`/`_underdark`/`_deep_underdark`);候选评审件与终稿分目录(`artwork/candidates/` 承载评审件)。验收:机检〈命名与目录检查器零红〉
@@ -557,7 +557,7 @@
| 参考 | 取 | 弃 |
|---|---|---|
| **Into the Breach** | 格内信息分层、攻击预告上格范式、战棋地块质感骨架(§1.2 原则一;§3.2) | 冷暗军事色调、压暗信息板、斜切 HUD(原则二;§3.3) |
| **Moonlighter** | 暖质地砖与砖缝质感、亲和冒险者气质、木石暖色道具(原则二/原则三);**光照层次与明度纵深**(亮地板/暗深渊/发光体三档,§2 明度分层) | 全拟物倾向(§3.3 半拟物边界);Darkest 式情绪压暗(§9.2) |
| **Moonlighter** | 暖质地砖与砖缝质感、亲和冒险者气质、木石暖色道具(原则二/原则三);**光照层次与明度纵深**(亮地板/暗深渊/发光体三档,§2 明度分层) | 全拟物倾向(§3.3 半拟物边界);Darkest 式压暗(§9.2 反面基准) |
| **Peglin** | 明快全年龄洞穴色彩、胖体量亲和怪(§5.1 普通怪原型) | 色块依赖——先读轮廓后读颜色(§5.1) |
- **反面条款已成文**(§9.2);反面参考**图证**待本作素材生成后增补。验收:方向性
@@ -566,7 +566,7 @@
| 反面参考 | 违反条款 | 立法理由 |
|---|---|---|
| **Darkest Dungeon** 式压暗 | §2 情绪明暗禁令(明度分层下仍禁) | 状态与情绪只走色相/饱和/运动/元素四手段,不以变暗表达情绪(失败重开,世界无光暗记忆) |
| **Darkest Dungeon** 式压暗 | §9.2 反面基准(风格取向记录,非禁令——压暗属普通设计决策,逐案走样张与目检) | 失败重开,世界无光暗记忆——明亮基调下不以整屏变暗作常态表达 |
| **传统扫雷白牌/贴纸式数字承载** | §1.2 原则二(明度分档为骨) | 数字由格底明度分档承载、直画入世界;色牌仅复杂格底后备——满屏白牌=电子表格观感,非游戏画面 |
| **Enter the Gungeon** 式粒子与色相爆炸 | §3.4 焦点预算;§4.2 色相纪律 | 信息层禁止被特效与杂色吞没,特效入画先过焦点预算 |
| **Hades** 式特效密度 | §2.2 兑现爽感的上限 | 爽感靠逐行因果归因,不靠满屏特效;承载元素以 §2.2 清单为界 |
+1 -1
View File
@@ -176,7 +176,7 @@ python tools/shot_checker.py <截图目录>
python tools/shot_checker.py <截图目录> shot_menu.png shot_battle.png
```
**判据或口径**:A 屏级骨架——PNG 存在、任一边 1280、灰度 std 不低于 8、唯一色数不低于 64。B 文字对比——战斗 C 区面板与明细带提示的 WCAG 不低于 4.5:1。C 明度三档——战斗屏信息面高于世界中,世界中高于世界底。D 文字禁压纹理——文字框背景中带灰度 std 不高于 18。F 文字在——菜单标题框内与中位灰度差大于 60 的像素占比落在 2% 到 55%。B/C/D 只在清单含 `shot_battle.png` 时跑,F 只在含 `shot_menu.png` 时跑,其余屏只跑 A。全过 0,缺参数或任一失败 1。
**判据或口径**:A 屏级骨架——PNG 存在、任一边 1280、灰度 std 不低于 8(取值在圣经 §7.2 文字 4.5:1 推论下界之下、正常实测最低 19.3 之上)、唯一色数不低于 40(正常实测地板 60 的 2/3,低于一切正常屏;色数异常回落=画面异常信号)。B 文字对比——战斗 C 区面板与明细带提示的 WCAG 不低于 4.5:1。C 明度三档——战斗屏信息面高于世界中,世界中高于世界底。D 文字禁压纹理——文字框背景中带灰度 std 不高于 18。F 文字在——菜单标题框内与中位灰度差大于 60 的像素占比落在 2% 到 55%。B/C/D 只在清单含 `shot_battle.png` 时跑,F 只在含 `shot_menu.png` 时跑,其余屏只跑 A。全过 0,缺参数或任一失败 1。
**已知坑与适用边界**:截图必须由窗口化 `shot_probe` 产出;headless 没有渲染面,截图是空的。`shot_meta.json` 缺失时 B/C/D 直接判 meta 几何缺失,不会跳过。尺寸断言按「任一边等于 1280」放行;1920×1080 截图两边都不等于 1280,会被判非工程档,与文件头「1920 档 1.5×」说明不一致,按源码为准。焦点预算是目检项,机检不判。
+33 -40
View File
@@ -1,4 +1,4 @@
# 生图提示词卡(角色级资产)v2.12
# 生图提示词卡(角色级资产)v2.13
> **性质**:美术清单生图通道(✦ 用户自跑,现走线上 API)的执行侧文件——每资产一段**自包含完整提示词**:正向 = 通用底座+主体+动作已拼妥、负向 = 通用底座+增补已并妥,整段复制直接贴进工作流即可,**不需要读其他文件、不需要拼装**。人物规格转写自 [character-design.md](character-design.md)(人物设计 v1.0,自 art-bible §5 迁出,圣经 §5 为存根),风格/色彩条款转写自 [art-bible](art-bible.md)(§4.1 主色板 / §4.2 语义色规则);冲突时人物规格以 character-design 为准、色彩以 art-bible 为准。
> **边界**:本文件只管**角色级**资产的生图通道。图标/格面/样张走脚本管线(⚙ 代理出),规格在 art-bible §4.6 与 [art-assets-checklist](art-assets-checklist.md),不需要提示词。**场景级资产(营地场景图的背景层与八个物件层)另立 [camp-scene-prompts](camp-scene-prompts.md);战斗舞台背景层与连续地面层另立 [battle-stage-prompts](battle-stage-prompts.md),本文件不覆盖**;两卡的角色/场景分工以「谁的资产谁出卡」为界,同一段提示词不两处维护。战斗舞台的小石台件与护甲类型图标三枚的**出图工单**登记在本文件 §5;§5 只把各权威节的定值收进逐件工单,不立新值。意图动画三档是动画资产,等一屏样张锁方案后另立规格卡,本文件不覆盖。
@@ -290,7 +290,7 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
> **定值纪律(§8 头注 定值单点纪律)**:本节**指路不立值**——色值 = 圣经 §4.1(含场景环境子板)、尺寸与缩放 = §8.2、明度带 = §2、甲档 = character-design §5.3、命名 = §8.3 + asset-path-map。本节新增零定值;凡圣经未定的值,一律进本节 §5.5 缺口,不在本节填空。
> **验收标注纪律**:逐条按三档标 `←机检: <断言>` / `←目检: <判据>` / `←方向性`,照圣经头注 2026-10-06 用户拍板的写法。
> **流程**:出图前后一律走 §0 五步;出图登记义务照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,其余参数有则记),随候选图存 `artwork/candidates/<资产名>/`;登记不作正稿遴选前置。
> **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`scene_stage_plaza.png` 48×12、`ui_armor_light/ward/heavy.png` 24×24,路径见各组 ① 字段;登记=战斗一屏样张批,合并提交 3b66d1a6);组一背景插画已迁至 [battle-stage-prompts](battle-stage-prompts.md)(仍待出图),组四立绘重产仍待出图。
> **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`scene_stage_plaza.png` 48×12、`ui_armor_light/ward/heavy.png` 24×24,路径见各组 ① 字段;登记=战斗一屏样张批,合并提交 3b66d1a6);组一背景插画见 [battle-stage-prompts](battle-stage-prompts.md)(三张背景+共用地面已入包,四柱/平色底退役归代码批),组四改按档派生(不重画,待实装批落位)。
### 5.1 组一 · B 区舞台背景插画(1 件)
@@ -375,25 +375,27 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
- ←方向性: 冷灰与全作暖底成材质对比(§5.3),不靠明度差与地台 `#968264`(L≈0.234)抢读——立绘与地台的可辨归 §5.1 轮廓通道
- 来源与工艺:♻ 素材站 plate armor 肩甲/胸甲类图标改色 → 闸四道;⚙ 兜底 24×24 内肩 + 胸两块甲板,与全作暖底成材质对比(§5.3 冷灰金属与暖底对比)
### 5.4 组四 · 主角与三 Boss 立绘重产(4 件)
### 5.4 组四 · 站桩立绘按档派生(9 件,不重画)
**四件共用字段**
> 本节是站桩立绘的**按档派生**工单:现有形象即终稿、禁改形象;清晰度靠保留原稿分辨率+显示按三档绝对高缩放解决,不重画、不生成。
**九件共用字段**
| 字段 | 值 |
|---|---|
| ① 路径与命名 | `game/assets/art/character/character_hero_portrait.png` + `game/assets/art/character/character_enemy_<enemy_id>.png`(域键 `character`,对象键 = `hero` / enemy_id,命名照 §8.3 与 asset-path-map §3 现役写法) |
| ② 尺寸档与像素网格 | **源档高逐键** = 主角 48 / ogre_chief 74 / drider 90 / duergar_king 94(§8.2 立绘源档表,逐件值见本节 5.4.1–5.4.4);**宽随源图宽高比,纵横倍率恒等**(§8.2 站桩立绘槽位 = 高定宽随条,禁把立绘塞进统一方框强拉);九宫格 = 否;显示一律 **×2 整数档** = 96 / 148 / 180 / 188;**乙类**(角色与单位精灵)——乙类允许非整数,本批按 §8.2 定案走 ×2 整数档 |
| ③ 色板约束 | 进**语义/UI token 板**(§4.1 分层①);**48 余量 0**(2026-10-28 用户裁:本组新增卓尔浅紫灰皮 1 枚,全板余量 2 → 0,额度已用尽)。逐件沿用 character-design §5.4 / §5.5 已登记色值;该两节只给形容词未给色值的部件,**2026-10-28 用户裁已落值**——四处换色合并(零额度)+ 卓尔浅紫灰皮新增 1 枚,逐件取值见本节 5.4.1–5.4.4,额度账见 §5.5 缺口 4。**石君王岩石板甲 = 重甲甲材质色、与 §5.3.3 同一枚,工单直接引 `#868C9A`、不另计额度**。单张像素域色数 ≤32。第二道门同本节 5.1 |
| ② 尺寸档与像素网格 | **源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;**显示档 = 三档绝对高**——小 96(地精/巨蜘蛛/灰矮人)、中 144(主角/钩爪恐魔/熊地精)、大 188(食人魔酋长/蛛化卓尔/灰矮人石君王),定值单点 = art-bible §8.2;显示倍率 = 档高 ÷ 源高,NEAREST,宽随源图比例(高定宽随、纵横倍率恒等,禁方框强拉);九宫格 = 否 |
| ③ 色板约束 | 色值以**现役稿**为准,本批零新增色、零换色;单张像素域色数 ≤32(`pixel-1` 与手修 2× 稿实测均 ≤32)。`#A79CB8` 保留在板、本批不消费,色板额度议题另批讨论 |
| ④ 明度带归属 | **世界中层 L 0.22–0.45**(§2 世界中层 = 格底顶面 / 角色 / 物件)——判定对象 = 角色体块主色。描边与落影底盘属轮廓通道(character-design §5.1)、紫赤点缀属 §4.2 色相独占的语义色,两者都不按明度带判定 |
| ⑤ 是否程序化件 | 否 → **须手绘**。口径 = **精细手绘像素**(2026-10-14 用户裁:仍是像素风,手绘像素簇 + 更高源密度;不是插画、不是 HD-2D、不回退高清图);**禁 AI 降采样当终稿**。现行 AI 降采样立绘 = 占位态,按 §7.1 占位完成条款须在终稿闸前换代 |
| ⑤ 是否程序化件 | 否 → **零绘制**:源 = 现有原稿,本批不生成、不重画(现有形象即终稿)。§7.1 占位完成条款对本组不适用——现役稿即终稿 |
| ⑥ 验收判据 | 见下方共用验收 |
| ⑦ 出图操作要点 | 见下方工艺要点(手绘,不产生图提示词) |
| ⑦ 操作要点 | 见下方派生要点(零绘制,不产生图提示词) |
**共用验收(四件都要过)**
- ←机检: 源档高逐位等于 48 / 74 / 90 / 94,显示 = 源 ×2(96 / 148 / 180 / 188),过滤 = NEAREST
- ←机检: 逐件显示档高与归档一致(小 96 / 中 144 / 大 188),过滤 = NEAREST,纵横倍率恒等;源高低于档高者按已接受放大项登记(主角 92 / 钩爪 94 / 熊地精 123 / 地精 90 / 巨蜘蛛 81)
- ←机检: 单张像素域色数 ≤32
- ←机检: 脚底基线对齐(派生管线四条:等比整数缩放 + 降色 ≤32 + 脚底基线对齐 + 命名合规)
- ←机检: 脚底基线对齐(紧裁 + 底中锚;命名合规、像素域色数 ≤32 同旧)
- ←机检: 主角描边 = `(40,19,13)`;敌方描边取暖白族(非黑、非深褐)—— character-design §5.1 主角描边例外条
- ←机检: Alpha 直出——不透明区外缘 1px 环内无生成底残留(无 R/G/B 三通道均 ≥230 且 alpha > 0 的像素),防抠图白边
- ←机检: 零 HSV 300°–345° 像素(drider / duergar_king 两 Boss 除外,§4.2 独占)
@@ -401,48 +403,39 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
- ←目检: 放大 4 倍,轮廓与衣服是成组的像素,不是降采样噪点(三判据①)
- ←目检: 显示 96–188px 下脸、铲、旗、物种能认出来,不必先读名字(三判据②)
- ←目检: 显示仍用邻近采样,不出现插画那种平滑渐变(三判据③)
- ←方向性: 精细手绘像素气质——不是插画、也不是半像素半高清
- ←方向性: 现有形象即终稿,本批禁改形象
三判据出处 = `reports/美术审计-cursor会话存档-2026-10-14.md` §2.2。
**工艺要点(手绘)**
**派生要点(零绘制)**
1. 出件顺序 = 先主角、再三 Boss(Alpha 范围 = 主角 + 三 Boss 重产,其余敌种当前可读则保留到 Beta)。
2. **源密度判据 = 三判据②**:显示 96–188px 下脸、铲、旗、物种可认——照这条反推源档即可,逐键定档已完成(§8.2),本批不必再猜档位。
3. 手绘起稿顺序 = 剪影(§5.1 archetype)→ 体块主色 → 暗部与受光 → 描边 → 落影底盘。
4. 逐件 2–3 版候选;登记照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,手绘作者有则记),随候选图存 `artwork/candidates/<资产名>/`。
5. 候选评审 = 并排横评,**隐去文件名与来源后按三判据逐条判读**(§0 五步第 1 步「便于横评」的手绘侧落法),不靠记忆与印象拍板。
6. 出件 = 透明 PNG(Alpha 直出,禁先出白底图再抠);入包前过 §8.3 色板检查器(报新增色 / 近色告警 / 超 48 上限拦截)。
1. 出件顺序 = 先主角三态,再三 Boss,再其余四敌(同管线,无绘制差异)。
2. 每件 = `pixel-1`/手修 2× 稿紧裁(去透明边、底中锚)→ 原样落 `game/assets/art/character/character_*`;**不做 `dom_block_resize`**。
3. 入包前过 §8.3 色板检查器与 standee_asset_check(按 §8.2 显示档校验逐件档高与纵横倍率)。
4. 桩盒宽 150 与 >150 溢出档的实装口径归代码批(art-bible §8.2 排版条);食人魔超宽 8px 为已接受项。
**5.4.1 主角(源 48 / 显示 96)**
**5.4.1 主角(中档 144)**
- 形象 = character-design §5.4 形象锚逐字执行:蓝兜帽取下披肩、暖米白上衣、深棕工装裤、额前护目镜、半指手套、卷裤脚皮护膝、圆头挖掘铲、结构式冒险背包配朱红矩形旗、腰挂提灯
- 已登记色:兜帽 `#2E86DE` / 上衣 `#EFE3C8` / 长裤 `#6B4A2E` / 皮革 `#8A5A34` / 眼 `#33261B` / 旗 `#C63D22` / 描边 `(40,19,13)`
- 已落值(2026-10-28 用户裁):**铲刃浅铁灰 = `FOG_MEMORY_INK #C4C4C4`**;**提灯铜色 = `GOLD_DEEP #B07A14`**——两件均走换色合并、零额度(character-design §5.4 对这两件只有形容词,账见 §5.5 缺口 4)。代价已随裁定接受:`#C4C4C4` 一色两岗(语义岗 = 迷雾记忆墨 §6.5)、`#B07A14` 属金族(§4.2 金只说收益与兑现);两者均为角色随身件材质色,不进信息层与语义物件岗
- 破例记账:背包 + 旗 = 主角对 §5.1「对称」的唯一破例组,旗面朝向恒定画右;轮廓凹凸预算按背包 + 旗整组记账
- 朝向:战斗画面的侧身面向怪物形象属状态图(§5.4 生成顺序口径);重产沿用现役朝向,不重新裁定
- 形象 = 现役稿即终稿,character-design §5.4 形象锚已兑现于现役素材;朝向沿用现役,不重新裁定
- 色 = 以现役稿为准(§5.5 缺口 4 的额度账随色板议题另批处理)
- 源 = `hero-ref/edit/normal_2x.png`(内容 88×92);attack/hit 用 `attack_2x.png`/`hit_2x.png`
**5.4.2 食人魔酋长 ogre_chief(源 74 / 显示 148)**
**5.4.2 食人魔酋长 ogre_chief(大档 188)**
- 档位 = 精英档(形状仍守精英档:普通怪形 + 恰一处垂直延伸 = 头顶骨与羽冠,底盘不变;第一关关底 Boss 战,不升 Boss 形)
- 甲档 = 轻甲 → 甲面覆盖率 ≈ 0:皮条 / 骨饰点缀、裸身为主,零甲板零符纹
- 主色域 = 暖褐皮、圆肚;**暖褐皮 = `暖石 #B8A582`**(2026-10-28 用户裁:换色合并、零额度;体块主色带 §5.4 ④ L 0.22–0.45 校验 L≈0.387 ✓;character-design §5.5 该行原只有形容词)
- 档位 = 大型(三场 Boss 战全归大型);形状仍守精英档剪影——普通怪形 + 恰一处垂直延伸(头顶骨与羽冠),底盘不变
- 甲档 = 轻甲(皮条/骨饰点缀、裸身为主,零甲板零符纹)——现役稿已如此
- 源 = `ogre_chief/pixel-1.png`(158×189);显示宽 157 超桩盒 150 为已接受项
**5.4.3 蛛化卓尔精灵 drider(源 90 / 显示 180)**
**5.4.3 蛛化卓尔精灵 drider(大档 188)**
- 档位 = Boss(敌类中唯一允许锐角与不对称者,体量最大)
- 甲档 = 魔法护甲 → 符纹饰带(暖白,覆盖率居中);蛛腹几丁质属天然甲壳,不计甲档
- 独占:紫赤 `#8F2F6A`(色相 323°)+ 形状独占双通道;全项目色相 300°–345° 只许本件与 5.4.4 出现
- **卓尔上体浅紫灰皮 = `#A79CB8`**(2026-10-28 用户裁:**新增 1 枚**,入 §4.1 分层①——在册紫族四枚全部语义岗占用且 L <0.22,无一可复用)。体块主色带 L 0.22–0.45 校验 L≈0.355 ✓;色相 264° 避 300°–345° 禁入带,紫赤独占仍归 `#8F2F6A`。**对比实测(本节新算,此前未给值)**:对深素底 `#282220` **6.04:1**、对舞台底 `#3E362C` **4.57:1**——两处均过 §4.1 图形 ≥3:1 门槛
- 大体档:显示 180 > 150 → 允许向上溢出盒顶(§8.2 站桩排版③ 甲案;溢出量 N 由实装批用真图复算 x 区间定,血条仍落底)
- 档位 = 大型;紫赤 `#8F2F6A` 色相独占 + 形状独占双通道维持现役稿
- 甲档 = 魔法护甲(符纹饰带)——现役稿已如此
- 源 = `drider/pixel-1.png`(140×189);皮色 `#A79CB8` 保留在板、本批不消费
**5.4.4 灰矮人石君王 duergar_king(源 94 / 显示 188)**
**5.4.4 灰矮人石君王 duergar_king(大档 188)**
- 档位 = Boss(锐角与不对称,体量最大档)
- 甲档 = 重甲 → 金属甲板(冷灰,覆盖率最大)
- 独占:紫赤 `#8F2F6A` + 形状独占双通道;大地 / 岩石主题
- 岩石板甲与灰皮(2026-10-28 用户裁,character-design §5.5 该行原只有形容词):**岩石板甲 = 重甲甲材质色,与 §5.3.3 同一枚 → 直接引 `#868C9A`,不另计额度**;**灰皮 = `STONE #786E62`**(按**次级皮**口径——石君王重甲覆盖率最大、板甲视同体块主色,故灰皮属次级皮、不受 L 0.22–0.45 约束;换色合并、零额度)
- 大体档:显示 188 > 150 → 溢出盒顶同 5.4.3
- 档位 = 大型(锐角与不对称);紫赤独占 + 岩石板甲维持现役稿色
- 源 = `duergar_king/pixel-1.png`(133×189)
### 5.5 缺口(四条 · 缺口 1/2/4 于 2026-10-28 已裁转指路,缺口 3 结论已立)
+2 -2
View File
@@ -17,8 +17,8 @@
| 素材 | 状态 | 需求文档 |
|---|---|---|
| 主角战斗小人(待机/攻击/受击三态) | ✅ B区站桩三态全接(2026-09-29 迭代 6 T3:normal/attack/hit 48px 档入包+挂事件切换+结算回 idle,短驻自回;棋盘 1.5×/脚锚25%/椭圆影在役不变;hit 态源=new-hit.pxo 已程序导出) | [character-design](character-design.md) + [art-prompts](art-prompts.md) §2 |
| 八敌种形象(L2 熊地精=bugbear 复用,不单列) | ✅ B区站桩八敌真图服役(2026-09-29 迭代 6 T1/T2:pixel-1 源→48px 档统一比例/底中锚/众数投票降采样,character_enemy_<键>.png 按常量入位,字形回退退役为缺位兜底;素材不入 git 本地在盘)。八敌源档已定(36–94,art-bible §8.2),旧统一 48 档为过渡形态,新档重产落位归 PRD(tools/pixelize_candidates.py `STANDEE_TIER_H` 已改,产出与定档总图一致) | [art-bible](art-bible.md) §5 + [art-prompts](art-prompts.md) §3 |
| 主角战斗小人(待机/攻击/受击三态) | ✅ B区站桩三态全接(2026-09-29 迭代 6 T3:normal/attack/hit 48px 档入包+挂事件切换+结算回 idle,短驻自回;棋盘 1.5×/脚锚25%/椭圆影在役不变;hit 态源=new-hit.pxo 已程序导出;2026-11-01 显示档改中档 144(源=手修 2× 三态稿,art-bible §8.2 v3.32),按档派生落位归本条实装批) | [character-design](character-design.md) + [art-prompts](art-prompts.md) §2 |
| 八敌种形象(L2 熊地精=bugbear 复用,不单列) | ✅ B区站桩八敌真图服役(2026-09-29 迭代 6 T1/T2:pixel-1 源→48px 档统一比例/底中锚/众数投票降采样,character_enemy_<键>.png 按常量入位,字形回退退役为缺位兜底;素材不入 git 本地在盘)。2026-11-01 显示档换代=三档绝对高 96/144/188(art-bible §8.2 v3.32),源 = 原稿不降采样,按档派生落位归本条实装批 | [art-bible](art-bible.md) §5 + [art-prompts](art-prompts.md) §3 |
| 意图准备动画三档 | ◐ | [art-bible](art-bible.md) §4.6.4 |
## P2 图标与格面(可与样张并行)
+1 -1
View File
@@ -27,7 +27,7 @@
|---|---|---|
| `character/` | 角色棋盘帧 + B 区立绘 | character_hero_idle/walk(棋盘 32×32 朝左基准)、character_hero_portrait(B 区立绘 46×48)+ character_enemy_<键> 八敌 |
| `fx/` | 特效帧 | shovel_swing_f0/f1/f2(挥铲三帧,握把锚 (18,12)) |
| `scene/` | **场景图**(背景层 + 地面层 + 围墙层 + 可点物件层;营地场景图首批) | 营地 10 张:`scene_camp_bg`(背景层,不透明全幅=天空+远山+地面+门洞小路)+ `scene_camp_wall`(围墙层,透明底、门洞留透明缺口)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件,不可点)+ `scene_stage_bg_dungeon` / `scene_stage_bg_underdark` / `scene_stage_bg_deep_underdark`(B 区战斗舞台背景层三关变体,✦ 插画级,顶部空区+远景天然洞壁,不画近景地面,按 board_type 选图)+ `scene_stage_ground`(B 区连续近景地面层,✦ 插画级,泥土/砂砾/碎石+顶缘分隔带,跨关恒定)+ `scene_stage_plaza`(B 区角色脚下小石台,48×12 九宫格件)——B 区战斗舞台背景与连续地面 2026-10-31 已入包;登记超前:营地场景图批落地前盘上未建 |
| `scene/` | **场景图**(背景层 + 地面层 + 围墙层 + 可点物件层;营地场景图首批) | 营地 10 张:`scene_camp_bg`(背景层,不透明全幅=天空+远山+地面+门洞小路)+ `scene_camp_wall`(围墙层,透明底、门洞留透明缺口)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件,不可点)+ `scene_stage_bg_dungeon` / `scene_stage_bg_underdark` / `scene_stage_bg_deep_underdark`(B 区战斗舞台背景层三关变体,✦ 插画级,顶部空区+远景天然洞壁,不画近景地面,按 board_type 选图)+ `scene_stage_ground`(B 区连续近景地面层,✦ 插画级,泥土/砂砾/碎石+顶缘分隔带,跨关恒定)+ `scene_stage_plaza`(B 区角色脚下小石台,48×12 九宫格件)——B 区战斗舞台背景与连续地面 2026-10-31 已入包;营地 10 张 2026-11-01 随迭代 47 卡④ 入包(`scene_camp_*` 十件在役、`TEX_CAMP_*` 十键消费;用户游戏内终审如有置换,按本行键名同径替换) |
| `board/` | 砖面/封印印记(三关换皮 + 封印印记件) | terrain_floor_<board_type>_<n>.png(三关地面系)、seal_mark.png |
## 4. 坐标/朝向等工程侧约定备查
+11 -7
View File
@@ -1,4 +1,4 @@
# 营地场景 · 分层生图提示词卡 v1.2
# 营地场景 · 分层生图提示词卡 v1.4
> **性质**:营地场景图(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 为准。
@@ -105,6 +105,8 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
| 笔记本 | 5 | 100×80 | 200×160 | §3.7 |
| 篝火 | 6 | 160×160 | 320×320 | §3.8 |
**落位实测(2026-11-01 迭代 47 卡④,锁定矩形 contain 呈报备案追认值)**:布告板 130×121/门扇 240×211/木桩 120×180/锻炉 240×193/帐篷 251×170/宝箱 106×90/笔记本 100×66/篝火 160×149——贴图锚定=底中心(帐篷/锻炉/宝箱/篝火/笔记本)+顶锚(门扇悬挂/布告板挂墙);命中矩形按锁定值。换算式登记(源 2× 档→落位尺寸)存 artwork 工作区,git 提交 3443890d 一线可考。
**3.1 布告板(→ 图鉴)**
```
A wooden notice board hung on a wall, with two or three blank pinned papers, no writing on them, one corner of a paper slightly curled; wood brown #8A5A34 frame, warm tan stone #C9AF83 papers.
@@ -162,6 +164,8 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
- 目检:密排竖木桩、桩顶削尖、不透缝、无横木;门洞 x1040–1520 **完全透明**(透出 bg 小路)、无门扇
- 单张色数 ≤32(量化后)、缩放为整数倍无拉伸痕迹
> **量化与缩放实况注记(2026-11-01 迭代 47 卡④ 落地)**:围墙层与八件已按营地主色板 **32 色重量化回写**(art-bible §4.1 子板行——违例对 0);落位缩放实况=源 2× 档经后处理换算为锁定矩形 contain(×0.1934–0.4404 非整数倍),「整数倍无拉伸痕迹」判据的机检面以落位实测尺寸与目检兜底执行。
**八件**
- 单件居中、四周留边、整件完整不出画
- 底色均匀 `#F2F2F2`、奶油描边闭合(去底脚本要能识别到屏障)
@@ -171,10 +175,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),本卡仍不复述色值
+2 -2
View File
@@ -42,7 +42,7 @@
- **兜帽披肩**:兜帽**不戴在头上**,取下披于肩上——露出棕色短发与整脸(表情可读),蓝色兜帽披在肩头仍是最近的"己方"识别点;三态同款披肩。
- **圆头挖掘铲(武器)**:持铲不持剑——**铲 = 本作核心动词"挖安全格"的角色化**,挖掘与攻击共用同一动作语义(攻击表现即挥铲,后续攻击类表现优先围绕铲展开)。画法:圆头铲刃 + 木柄(木柄走木色族 `#8A5A34`,铲刃浅铁灰、不做冷灰重甲质感);**待机态 = 铲插立在地上、一只手扶在铲柄顶端**;攻击/受击态仍为手持。刀光/铲击光效/弹道不进精灵图(归战斗演出另立资产)。
- **冒险背包与背包旗(身份件)**:背负**结构式冒险背包**——翻盖+皮扣带的帆布背包(暖卡其布面,皮革件走木色族),**宽 ≈1.5× 体宽**:双肩背负、两侧各略超出身形一点;**禁圆麻袋化**(措辞弃"鼓胀掳获袋")、**禁特里式 3× 超宽**(反面参照:旷野之息特里;正面参照:合金弹头双主角大背包、超级马里奥奇诺比奥队长)。**旗杆插在背包一侧**(不要求完整可见,**被头部遮挡一部分亦可**),旗子**尺寸大于头部**,短杆**矩形**旗旗面 = **旗朱红 `#C63D22`**(人设背包旗与棋盘封印色**脱钩**——封印色仅作用于棋盘格封印旗,色史归 art-bible §4.1;人设旗形为矩形非三角,以 normal/hit 定稿成图为基准),把"宣告落旗"的角色化焊在人设上,主角即背包旗持有者(**动作呈现名 = 封印**,见 art-bible §3.2;背包旗**不**视作封印印记的携带形态——旗面旗朱红矩形,与棋盘封印黑旗形色均不同,仅保留"主角即背包旗持有者"的身份叙事)。三条纪律:①旗面红=旗朱语义**仅旗非服饰**(负向 `no red clothing` 不拦截正向 `red flag`——红服饰禁令与红旗的边界由正向句显式锁色值;封印黑仍为棋盘格封印旗专属色,圣经 §4.1 不变);②背包+旗 = 主角对 §5.1"对称"的**唯一破例组**(语义配件非装饰),旗面朝向恒定画右、跨素材一致;③轮廓凹凸预算按"背包+旗"整组记账,样张(圣经 §1.4)验证背负后剪影仍窄、仍可辨、不超格宽。
- **护目镜与腰灯**:**护目镜推在额前**(兜帽披肩后直接戴于额上,不戴眼上——眼睛是表情载体;皮革带+圆镜片,防尘挖掘者的职业信号);**提灯挂腰间,定案不手持**(小号铜色提灯,**不点亮**;空间光照属圣经 §2 明度分层允许域,**提灯点亮为候选**,点亮形制(光晕半径/暖色值/与舞台光照关系)归整屏样张重议;头部饰品唯一化,头灯方案已弃)。
- **护目镜与腰灯**:**护目镜推在额前**(兜帽披肩后直接戴于额上,不戴眼上——眼睛是表情载体;皮革带+圆镜片,防尘挖掘者的职业信号);**提灯挂腰间,定案不手持**(小号铜色提灯,**不点亮**;空间光照走圣经 §2 明度分层,**提灯点亮为候选**,点亮形制(光晕半径/暖色值/与舞台光照关系)归整屏样张重议;头部饰品唯一化,头灯方案已弃)。
- **衣着细节**:**棕色半指手套**(挖土护手,露指保灵巧读感)+ **裤脚卷起、棕色皮护膝**(挖掘跪地的工装感)——小件走皮革木色族,不占剪影凹凸预算,不新增语义色。
- **三态**:① 待机 = 窄、直立、对称(§5.1 小人原型,含背包、旗与提灯记账);② 攻击 = 允许破对称,整体仍窄、不超格宽;③ 受击 = 无血红、无伤害数字(伤害数字归 HUD 层,不进精灵图)。
- **随身件 = 秘宝形象候选池**:铲、旗、背包、提灯四件随身件是未来秘宝设计的**形象候选池**——道具实装可借用主角身上已有的同款(彩蛋闭环:玩家在道具栏认出主角身上的物件);具体道具语义/数值归秘宝 GDD,人设侧义务 = 四件画得出来、认得出。
@@ -67,7 +67,7 @@
- **主色域 = 物种本色**,不受圣经 §4.2 语义色禁则约束(该禁则管格面与 UI 的逻辑色,角色身体主色豁免);**两例外**:禁入带 300°–345° 全角色禁入(仅 drider/duergar_king 两 Boss 独占)、蓝族禁入敌方主色(§5.4 同款)。
- **L2 熊地精 = bugbear 复用**(第二关三行,显示名熊地精系,非独立键):同剪影**换色板不换形**(色板替换走脚本管线——原则三"先换色板"的角色侧应用);变体主色避小人蓝族与禁入带。
- 每敌种出 2–3 版候选(清单 §2.2);受击反馈/死亡表现/顺位标记三类呈现下限见圣经 §4.6.4 资产需求行。
- **立绘源档高(定值单点 = 圣经 §8.2 立绘源档表,本档指路不复述)**:八敌 36–94 逐键定档 + 主角 48 不变、显示一律 ×2 整数档(NEAREST)——**数值权威一律圣经 §8.2**(定值单点纪律,圣经 §8 头注);定档经三体型约束校验(§5.2)确认;派生管线与排版规范同归圣经 §8.2。
- **立绘显示档(定值单点 = 圣经 §8.2 站桩立绘显示档条,本档指路不复述)**:三档绝对高 96/144/188,逐件归档、源档与缩放规则一律见圣经 §8.2——**数值权威 = 圣经 §8.2**(定值单点纪律,圣经 §8 头注);归档经三体型约束校验(§5.2)确认;派生管线与排版规范同归圣经 §8.2。
- **钩爪恐魔「宽≥高」豁免登记(待样张终审)**:定档源 58 高窄条束状剪影(长臂圆钩),宽高比 <1,与 §5.1 普通怪「近圆墩形宽≥高」冲突——按体量=生物天性(§5.2)登记豁免:以长臂束状轮廓为识别锚,不强行压成墩形;样张(圣经 §1.4)终审复验灰度剪影可辨性。
## 5.6 意图准备动画三档(规格承载节)
@@ -2,8 +2,8 @@
## 文档状态
- 版本:1.26
- 最近更新:2026-10-30(迭代 42 反哺点落文——ShopSession 行对外暴露格按委托壳拆除后实测直连面重写:完整直连方法清单+set_shop_pool 调用面改述;沿革查 git)
- 版本:1.28
- 最近更新:2026-11-02(迭代 47 反哺点落文——营地域六件归属行增补〔UiCampModals/CampSceneView/营地四子界面视图族/camp_esc_overlay+MainEscFlow/CampHudBand/CampRefreshBus〕+D1 营地面现状化+ui_registry 行子容器与合同槽换代;沿革查 git)
- 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准)
@@ -89,7 +89,7 @@
| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10/类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW |
| **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,已建件——迭代 41 存量债批) | 存档内元进度数据的读写唯一层(自 save_manager 收编 accessor 族:钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 写口原语 13+只读投影 6(tree_state/forge_state/attune_state/career_state 四投影+keystone_balance/item_unlocked)=公开 19(add_keystone 私有 `_add_keystone`),零 _data 副本零缓存;MetaProgression 业务层与营地/锻造 UI 消费方经 `save.meta_state.` 直连(meta 的纯转发读方法已退役) | save_manager(序列化接线);FileAccess——LOW |
| **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 |
| **action_economy**(A3) | AP/RAP 账本、last_round_end、轮次相位机 {Idle, Active, Waiting}、结束仲裁 | 意图与通知布尔面:notify_round_start / request_dig / request_stop / request_refresh / notify_settlement_done / notify_combat_end;预授权通道:grant_reserve_ap / finish_round_stop;ap/rap/phase 只读 | ConfigDB(ActionEconomyTable);effect_engine(事件发射 dispatch——基础层内同层引用);B1 回发 next_round_mode/battle_over | 无 |
| **action_economy**(A3) | AP/RAP 账本、last_round_end、轮次相位机 {Idle, Active, Waiting}、结束仲裁;dig_paid 命中施加抽私有单点(_apply_dig_paid_hits——矿脉储能入池面) | 意图与通知布尔面:notify_round_start / request_dig / request_stop / request_refresh(收折后价入参——折价计算归 BattleRefreshFlow 单点)/ notify_settlement_done / notify_combat_end;预授权通道:grant_reserve_ap / finish_round_stop;ap/rap/phase 只读 | ConfigDB(ActionEconomyTable);effect_engine(事件发射 dispatch——基础层内同层引用);B1 回发 next_round_mode/battle_over | 无 |
| **variation 定义**(A4) | VariationTable schema(现行 6 行:六 region——贫矿脉 global 行 2026-10-16 用户令屏蔽删行,global 型机制代码保留)与 F1 候选池门控谓词 | `variant_pool(level_index, board_type, node_type="")`(门控三维:unlock+board_types+节点身份——global 行仅 boss/elite 入池,缺省跳过零扰动);`global_variant_row(level_index, board_type, node_type)`(全局型单行决议访问器) | ConfigDB(VariationTable) | 无 |
| **status_container**(B3) | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 5 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB(StatusEffectTable) | 无(RefCounted) |
| **effect_engine**(内容数据驱动扩展批 iter17 建域) | 零运行时可变状态的系统(见「架构原则」条 8):全同步解释器——事件由宿主在明确代码点位显式调 dispatch 进件,命中序=装载声明序(feats→treasure→树规则)写死保种子复放,不是事件总线/事件循环;事件/事实/效果类型/算子四词表封闭 + 树规则合成订阅六键(posture 每关首轮窗/sense 两组限次 counter 闸·adv_ 前缀走冒险桶跨战斗持久) | rebuild(每战重建订阅表)/`dispatch(ctx, event, payload, facts)`/`validate_trigger_row` + 静态计数口 `counter`·`counter_add`·`counter_spend`·`bucket_of`·`counter_id_legal`(计数器读写只经 EffectContext 引用束,无导出/灌入接口) | ConfigDB(feats/treasure 行集)、EffectContext(四桶引用由宿主持有:round/battle 桶归编排器、level 桶归关卡域、adventure 桶归应用层——RunLedger 已随迭代 39 拆件批退役,桶字典由 adventure_runner 持有) | 无(零随机全确定) |
@@ -119,7 +119,8 @@
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层 + 字段分层,词表以 owner「明细流水」节为准)、条款③⑤闭合对账、**战斗域全部跨系统调用权**、效果引擎宿主面与树规则挂点施加面(**已拆出 BattleFxHost 并实装(2026-10-28 迭代 40——battle_fx_host.gd/reward_grant.gd 两件在库,效果引擎宿主面归 BattleFxHost 行承载,拆件细则见「战斗编排域拆件定稿」节);2026-10-29 零豁免裁设计闭合:RoundCycle/BattleFacts 两新件随批(宿主 38→15、S13 豁免第三条注销——见下两行与定稿节轴丁)**) | `begin_battle(encounter)`;窗口/刷新意图提交面(扮演 D1);`level_clear()`;步骤流(输出契约) | B2/B3/B4 全调用面(**唯一调用方=B1 域文件集:battle_orchestrator.gd + settlement/ 五段段类 + damage_controller + 拆件后三新件**);A3 request_refresh 代理 + 回发;A2 install/retire + 三事件订阅;A1 generate(attempt 非 scope 事件);D2 SettlementTable + set_reload_gate | 无(零随机、禁 await——切片 88 冒烟实证可回放) |
| **BattleFxHost**(`core/battle_fx_host.gd`,B1 域·效果引擎宿主件,2026-10-26 编排域拆件设计定稿——实现批建件) | 效果引擎挂点施加面唯一承载(自 battle_orchestrator 收编 fx 族):事件发射与事实判定(_fx_emit/_fx_fact)、主角 AC 基线与本轮/本战 AC 累积、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含 _fx_revealing 再入闸——同步信号回环防二次缓冲二次兑付)、树规则合成订阅的 round/battle 桶计数器持有(桶随编排器生灭语义不变) | 构造=引用束注入(防 _init max-parameters 新键);bind 时注入 emit Callable(行发射经宿主协议面单点)与兑付 Callable(RewardGrant 授予面——揭示队列 flush 回调);只读投影分两类消费方:AC 累积/bonus_delta 供宿主呈现快照与战末 drain,揭示态供宿主 A2 订阅旁路判定;`luck_bias_for(cell_count) -> int` 偏运合计查询口(体内三参形 dispatch——reward_roll 发射与 bias_rewards 求和自 RewardEconomy 收回本件,RewardEconomy 经注入 Callable 消费) | EffectEngine(EffectContext 引用束);RngServiceCore(fx_reveal 封闭流——除该流外零随机);A2 minesweeper(板读与 fx_reveal 调用);A3 action_economy(queue_ap_delta/charge_layers 读);B3 status_container 读;B2 unit_container 读(hp_ratio);DamageController(set_hero_ac_override);ConfigDB;encounter 引用 | 无(零 await——效果引擎全同步解释器) |
| **RewardGrant**(`core/reward_grant.gd`,B1 域·奖励授予件,2026-10-26 编排域拆件设计定稿——实现批建件) | 奖励授予唯一兑付管线(自 battle_orchestrator 收编 grant 族):极性字典路由与封印溢价兑付(_grant_routed)、三种行应用(flat/percent/gp)、伤害单据提交面(_submit 族)、清图奖励与身份授予、秘宝使用编排(use_treasure 四步)、战斗域 GP 缓冲 escrow 与授予段暂存伤害闸(_defer_board_damage/_deferred_board) | use_treasure 编排入口(treasure_interact_controller 经组合根现值回调直连);授予入口(结算段类直调);只读投影(escrow 读与 GP 掉落合计——宿主呈现快照与 _roll_gp_drop 消费) | B4 RewardEconomy(polarity_row/grant);B1 域 DamageController(开单);BattleFxHost 引用(treasure_used 事件发射与计数器消耗);A2 minesweeper(校验回调/fx_reveal/remove_mines_in);A3 action_economy(相位闸/grant_ap/queue_ap_delta);B3 status_container(apply_effect/池读);B2 unit_container(hero_unit/heal);ConfigDB;注入 emit Callable、宿主 fatal Callable(battle_result 状态机唯一写点留宿主)、宿主 attune 读投影(_attune_safe_seen)、encounter 账本引用;TreasureItems 经宿主 setter 转发注入(库存注入时序在宿主构造之后) | 无(零随机零 await——授予期零随机口径,roll 值授予前已定) |
| **damage_controller**(B1 域·伤害单据管线) | 伤害单据唯一兑现管线(单据九栏目声明→分量掷点/克制修正/伤害下限归一→明细行发射;双方同规) | 兑付入口(编排侧开单)+ fx 揭示回调注入位 | B2 单位实例(单据目标传入);B1 注入(EffectContext 引用束与编排回调) | 无 |
| **damage_controller**(B1 域·伤害单据管线) | 伤害单据唯一兑现管线(单据九栏目声明→分量掷点/克制修正/伤害下限归一→明细行发射;双方同规);`damage_taken` 唯一发射口(_emit_hero_damage_taken)+唯一消费族(_apply_hero_damage_effects)——敌方攻击/地形/危险格/错误封印直伤四路径统一发射,board_flat_hero 结账前统一发射 | 兑付入口(编排侧开单)+ fx 揭示回调注入位 | B2 单位实例(单据目标传入);B1 注入(EffectContext 引用束与编排回调) | 无 |
| **BattleRefreshFlow**(`core/battle_refresh_flow.gd`,B1 域·换图刷新流程件,已建件——迭代 46) | 弃图刷新流程唯一承载(自编排器收编):刷新可用性门(相位/每轮上限/储备门槛)、折后价单点(_effective_refresh_cost——专长行声明序第一命中、floor 取整、can 与执行同源)、换图执行序(扣储备→used+1→旧资源退役→旧板退役→装新板→board_refreshed 成功事实登记) | can_refresh/execute 两公开入口;调用路径呈现层→编排器两公开方法单行转发→本件;A3 request_refresh 收折后价入参 | 编排器(宿主,B1 域内唯一调用方约束同族适用);A3 action_economy(折价扣费与拒绝判据);A2 装板壳(装板与退役);RewardGrant(apply_board_refreshed 成功事实消费);feats 折价行经 ConfigDB;static_scan S7_B1_SET 已增补本件(判据口径不变) | 无(零随机零 await) |
| **BoardInstaller**(`core/board_installer.gd`,B1 域·装板域件,2026-10-29 尾波清偿批设计闭合——实现批建件,细则见「战斗编排域拆件定稿」节轴丙) | 棋盘安装与变异布置域唯一承载(自 battle_orchestrator 收编装板族六函数):生成受理、全局作用域变异决议、资源摇号资格域组装(roll_board 恰一次)、region 变异决议与连通片布置;BoardGenerator 实例随件自建自接线(宿主成员退役)——宿主侧装板后状态复位(封印/完美计数清零、板序推进、board_changed 发射、秘宝叠加层退役)留宿主薄壳,时序逐字保持 | `install(during_round) -> bool` 单入口;构造=引用束注入(rng/db/minesweeper/reward_economy/encounter 只读引用+宿主 fatal Callable——防 _init max-parameters 新键) | A1 board_generator(件内持有);A2 install_board;B4 roll_board/board_skew 写;A4 变异决议行集经 ConfigDB;board_gen/variant_place 两封闭流随件、流名与开流时点零漂移 | 无(零 await——装板全同步) |
| **RoundCycle**(`core/round_cycle.gd`,B1 域·轮循环驱动件,已建件——迭代 44) | 轮循环驱动域唯一承载(自宿主收编 7 成员):结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 三参直构(host/fx_host_ref/reward_grant_ref——五段构造透传不落成员);段驱动入口由宿主接线调用;实测类变量 8/公开方法 3 | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 |
| **BattleFacts**(`core/battle_facts.gd`,B1 域·战斗事实件,已建件——迭代 44) | 战斗事实域唯一承载(自宿主收编 14 成员):受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 零参直构;宿主与段类经注入引用读写;实测类变量 15(贴顶)/公开方法 4(reset_round+三 mark 写口) | B1 宿主;A2/A3 只读 | 无 |
@@ -131,7 +132,7 @@
### 战斗域装配现状(迭代 27——2026-10-17 用户裁写入;RunLedger 段已随迭代 39 拆件批退役,四账去向见下「冒险域拆分(现状登记)」节)
- **结算五段**:B1 编排器的每轮结算序列拆为 `game/core/settlement/` 五个段类——GrantSegment(①授予段,含封印判定与 counter/grant 助手单点)→ AuraSegment(②增益/减益段)→ CombatSegment(③战斗段)→ DecaySegment(④衰减段)→ VerdictSegment(⑤判定段,返回裁定,终局早退语义留 B1)。段序骨架冻结于 `SettlementTable.segment_order` 语义,B1 退为段序编排+接线;段类可直接 `new()` 单测(`settle_segments_test` 在役)。
- **结算五段**:B1 编排器的每轮结算序列拆为 `game/core/settlement/` 五个段类——GrantSegment(①授予段,含封印判定与 counter/grant 助手单点、成功刷新事实消费面 apply_board_refreshed 与轮末治疗计数兑付面)→ AuraSegment(②增益/减益段)→ CombatSegment(③战斗段)→ DecaySegment(④衰减段)→ VerdictSegment(⑤判定段,返回裁定,终局早退语义留 B1)。段序骨架冻结于 `SettlementTable.segment_order` 语义,B1 退为段序编排+接线;段类可直接 `new()` 单测(`settle_segments_test` 在役)。
### 战斗编排域拆件定稿(2026-10-26 用户裁写入——gdstyle 存量债波 1 设计闭合;同日独立审查(grilling+godot-master 双子代理)阻断 8 条去重 6 组全折入本节,v1.18;2026-10-28 迭代 40 实现批合回(合并提交 514457d1),本节为现状登记)
@@ -162,9 +163,15 @@
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|---|---|---|---|---|
| **D1 HUD**(呈现面拆分持续换代:组合根 main.gd + 三屏壳(MenuScreen/CampScreen/AdventureScreen)+ views/ 视图单元(现行承载面见「呈现层装配与会话生命周期」节;早期 9 单元清单与 LevelStripView 已随场景换代退役);营地覆盖层=四柜台并排〔树淬炼主界面/锻造工坊·秘宝匣钥匙门/图鉴常开——迭代 21 T6 程序占位重排〕) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务状态记录(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) |
| **D1 HUD**(呈现面拆分持续换代:组合根 main.gd + 三屏壳(MenuScreen/CampScreen/AdventureScreen)+ views/ 视图单元(现行承载面见「呈现层装配与会话生命周期」节;早期 9 单元清单与 LevelStripView 已随场景换代退役);营地面=场景根+四子界面功能窗口+Esc 浮窗+顶栏 HUD 栏〔迭代 21 四柜台覆盖层已随迭代 47 卡④⑤ 退役〕) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务状态记录(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) |
| **组合根拆件**(呈现侧写口子件:curio_interact_controller / camp_shop_presenter / treasure_interact_controller) | 代组合根执行核心写调用(冒险节点后续推进/营地购买与刷新/道具使用——依赖与回调由 main 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 |
| **ui_registry**(`presentation/ui_registry.gd`,界面注册面,已建件——迭代 44) | 呈现层接口面唯一承载:层序合同(LAYER_CONTRACT)迁驻与合同寻址、三域句柄容器 ui_screens(9 屏壳与覆盖层句柄)/ui_battle(5 战区句柄)/ui_modals(13 模态句柄+ModalFlow);组合根经其一级寻址接线,探针挂载假设迁移为 `main.ui.<域容器>.<句柄>`;七个探针别名/转发成员随批消灭 | 三容器属性+合同寻址口(RefCounted 门面,不挂树、零会话期引用);四件(UiRegistry/UiScreens/UiBattle/UiModals)类变量 3/9/5/14,ModalFlow 随 UiModals 容器构造即建 | 构造注入:三屏壳、覆盖层、战区句柄、ModalFlow(main_ui_build 交付随之改型) | 无(RefCounted) |
| **ui_registry**(`presentation/ui_registry.gd`,界面注册面,已建件——迭代 44) | 呈现层接口面唯一承载:层序合同(LAYER_CONTRACT)迁驻与合同寻址、三域句柄容器 ui_screens(9 屏壳与覆盖层句柄)/ui_battle(5 战区句柄)/ui_modals(平铺模态句柄+营地域 UiCampModals 子容器〔六句柄、两段式寻址 `modals.camp.*`——迭代 47 卡⑥〕+ModalFlow;层序合同营地槽换代=camp_overlay 退役、换 camp_scene+四子界面+camp_esc_overlay——迭代 47 卡④⑤);组合根经其一级寻址接线,探针挂载假设迁移为 `main.ui.<域容器>.<句柄>`;七个探针别名/转发成员随批消灭 | 三容器属性+合同寻址口(RefCounted 门面,不挂树、零会话期引用);四件(UiRegistry/UiScreens/UiBattle/UiModals)类变量 3/9/5/14,ModalFlow 随 UiModals 容器构造即建;UiCampModals 营地域子容器随卡⑥(ui_modals 类变量 14→14——删六平铺增一子容器引用) | 构造注入:三屏壳、覆盖层、战区句柄、ModalFlow(main_ui_build 交付随之改型) | 无(RefCounted) |
| **UiCampModals**(`presentation/ui_camp_modals.gd`,营地域句柄子容器,已建件——迭代 47 卡⑥) | ui_modals 营地域六句柄唯一容器(场景根/四子界面/Esc 浮窗/HUD 栏),句柄寻址两段式 `modals.camp.*`;ui_modals 平铺面收窄 | 子容器属性+六句柄只读投影;main_ui_build 装配交付 | 构造注入(六视图句柄);ui_modals(父容器挂载) | 无(RefCounted) |
| **CampSceneView**(`views/camp_scene_view.gd`,营地场景根视图,已建件——迭代 47 卡④) | 营地场景图唯一呈现件:十层贴图铺底(层序按 layout 表 z 序;`camp_scene_layout.tres` fail-fast 校验+七命中区几何红线——camp_scene_layout_test 承载)、七物件命中区命中即发 object_pressed(组合根路由大门/篝火/笔记本/四界面)、反参照纪律(场景根零文字按钮)、登记模态初始隐(boot 态不盖菜单) | 构造注入(贴图经 `UiKit.TEX_CAMP_*`、layout 表经 ConfigDB 表现域容器);object_pressed 信号外发;open/close 经营地域子容器寻址 | camp_screen(宿主挂载);ConfigDB;组合根(路由消费) | Control+TextureRect 树 |
| **营地四子界面视图**(`views/camp_sub_view_base.gd`+forge_view/tree_view/treasure_view/codex_view,功能窗口公共壳+四件,已建件——迭代 47 卡④) | 营地五功能窗口的四个承载视图(锻造工坊/天赋树/秘宝匣/图鉴)+公共壳(窗口壳/行板/关闭钮;生涯页挂笔记本路由不经此族);模态栈单口 open/close、五窗互斥单开;presenter 承接面零回表(树购买/锻造三意图经既有写口族) | 各 view open/close+意图信号外发;关闭钮 pressed | camp_shop_presenter/MetaProgression 写口(经组合根);CampRefreshBus(刷新广播消费) | Control |
| **camp_esc_overlay+MainEscFlow**(`views/camp_esc_overlay.gd`+`presentation/main_esc_flow.gd`,Esc 浮窗视图+四态路由件,已建件——迭代 47 卡⑤) | Esc 浮窗三钮视图(返回主菜单/设置/退出)+Esc 四态路由静态件(根态唤起/再按关窗/功能窗开着先关最上层窗〔camp.md v2.3 定稿覆盖卡面 Q1 甲〕/设置开态不误关;菜单语境不唤起);**main._unhandled_input=产品码首个键盘输入入口**(契约 9——事件驱动、禁 Input 轮询、不挂 ui_cancel、pressed 非 echo) | flow.open/close 路由口;三钮 pressed 意图信号(经组合根接线) | 组合根(_unhandled_input 前置与意图消费);模态栈(关窗单口);screen_flow(设置/返回动线) | Control+静态件 |
| **CampHudBand**(`views/camp_hud_band.gd`,营地顶栏 HUD 件,已建件——迭代 47 卡⑤) | 顶栏 48px 全宽窄条:钥石余额 badge 唯一呈现(单口取现值、显式刷新才变);set_hud_band 预留位落座 | badge 文本投影;refresh 时取现值 | CampRefreshBus(挂册广播);MetaProgression 钥石读 | Control |
| **CampRefreshBus**(`views/camp_refresh_bus.gd`,营地刷新单口件,已建件——迭代 47 卡④⑤) | 营地刷新单口唯一承载:进档尾与营地写口尾触发 refresh 广播(八写口尾改路由),现开界面与 HUD 同步取现值 | 挂册/refresh 广播 | 组合根(触发点接线);四子界面与 CampHudBand(消费) | 无(RefCounted 信号件) |
| **board_hover**(`views/board_hover.gd`,悬停预承诺呈现域件,已建件——迭代 45) | 悬停反馈域唯一承载(自 board_view 收编 11 函数族+_hover/_last_click 两成员):三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚;_r0 出生展开区成员留宿主 | 构造=引用束注入实为 7 项(cells/minesweeper/diggable 引用+格命中换算/光标写入/hover_forbidden/chord_attempted 四 Callable——卡片预估 5 项,11 函数逐字迁移的最小必需面);三入站口留宿主委托;hover_probe 读面留宿主 | board_view(宿主);输入事件经宿主入站口 | 无(RefCounted) |
| **cell_base_layer/cell_mark_overlay**(`views/`,单格两绘制域件,已建件——迭代 45) | 单格绘制域拆分(自 board_cell_unit 状态件 `_CellPaintState` 收编,入参与该层绘制命令同件):基座层(底面/描边/立面/投影/接触带/倒角,12 成员)与标识叠加层(标记/字形/徽章/封印/点迹/高亮/退色,13 成员);宿主 _draw 退为六步时序器,类变量 30→10 | 构造注入;绘制命令由宿主 _draw 按层序调用 | board_cell_unit(宿主);gdstyle 键各文件独立计数(12/13 均 ≤15) | 无(RefCounted) |
| **standee_vitals/standee_motion**(`views/`,站桩两状态件,已建件——迭代 45) | 站桩状态域拆分(自 `_StandeeState` 真实 25 成员收编):身份命值(hp/atk/状态值族,12 成员)与资产演出(动作/姿态/位移演出族,11 成员);三个演出探针计数器留宿主,宿主类变量 19→10 | 构造注入;st(沿用)/mo 两实例由宿主持有 | standee_body(宿主);battle_stage_view 写入面逐字保持 | 无(RefCounted) |
+3 -3
View File
@@ -10,8 +10,8 @@
主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;2026-10-30 的「主区直改+直提」车道与 commit 点 pre-commit 直改闸随之退役;验证模型不动——合回后本地 CI 仍是唯一强制验证点):
1. **worktree 车道(一切面)**:每批先建独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效)
2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → CI 验证 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter<N> 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**CI 全绿后才 `git worktree remove`+删分支;CI 红不删区——回任务 worktree 修复提交再合,追绿后删区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑
3. **合回后验证(本地 CI,2026-10-30 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh merge` 对 master 尖跑一次全量域闸——唯一强制全量验证点;CI 红态下每个修复提交经 post-commit 钩子(`tools/hooks/post-commit`)自动追绿复验(钩子仅主工作区生效——修复合回落地即触发),绿态与无状态零触发。重跑判据:同一份字节禁止第二次人手跑;有意义的重跑只剩「验证修复」一类。CI 红不回滚合并——红项记 `game/tests/.gates/ci-status.json`,开工先读 CI 状态,红未修不开新批。跑批起手 `--editor --quit` 刷类名缓存(在册坑:.godot 缓存不入 git,合并后失配假红假绿)。并跑语义=跑完补跑(coalesce):跑批期间新触发不中断当前轮,轮尾对最新尖端补跑,绝不逐笔各跑一轮
2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter<N> 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑
3. **合回后验证(本地 CI,2026-10-30 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh merge` 对 master 尖跑一次全量域闸——唯一强制全量验证点;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(红态修复走新开修复 worktree,`fix-<任务名>` 分支,非 iter<N> 形免重塑免数卡;post-commit 钩子保留为红态主区直提兜底触发,绿态与无状态零触发)。重跑判据:同一份字节禁止第二次人手跑;有意义的重跑只剩「验证修复」一类。CI 红不回滚合并——红项记 `game/tests/.gates/ci-status.json`,开工先读 CI 状态,红未修不开新批。跑批起手 `--editor --quit` 刷类名缓存(在册坑:.godot 缓存不入 git,合并后失配假红假绿)。并跑语义=跑完补跑(coalesce):跑批期间新触发不中断当前轮,轮尾对最新尖端补跑,绝不逐笔各跑一轮。**回执等待纪律(2026-11-02 用户裁)**:合回落地即向用户呈报本批总结,回执等待一律以 run_in_background 后台任务运行 `tools/ci_watch.sh <合并提交>`(认领判据与容错细则=该件头注),禁前台 sleep/轮询循环等 CI;后台任务唤醒会话后按回执续行——绿即删区收口,红或超时即呈报走修复批。闸重跑等一切分钟级跑批等待同此纪律
4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter<N> 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge/post-commit 钩子——本地 CI 触发(仅主工作区;任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层)
## 2. 五段流水线
@@ -49,7 +49,7 @@
**完成**
8. **合回后 CI 验证(唯一强制验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动跑全量域闸(不带参数档,02 全套件),判定与红项落 `game/tests/.gates/ci-status.json`。全量是唯一强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时 CI 判红在案(跑不出回执=verdict 红,fail-closed 语义保留)
9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复提交(post-commit 钩子自动追绿复验,每笔修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。类名缓存刷新与开机确认由 CI 跑批起手接管(§1 第 2 条),合并后无手工动作
9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复合回(post-merge 钩子复验,每轮修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。类名缓存刷新与开机确认由 CI 跑批起手接管(§1 第 2 条),合并后无手工动作
10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12
11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注
12. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置
+1 -1
View File
@@ -27,7 +27,7 @@
## 4. 验证时机(合回后本地 CI 制,2026-10-30 换代)
开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的本地 CI:git merge 合回 master 触发 post-merge 钩子跑 `tools/run_all_gates.sh` 不带参数档(两域全量,02 全套件——跨卡触面与关联档选测的盲区由全量兜底),结果落 `game/tests/.gates/ci-status.json`;CI 红态下修复提交经 post-commit 钩子自动追绿。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。
开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的本地 CI:git merge 合回 master 触发 post-merge 钩子跑 `tools/run_all_gates.sh` 不带参数档(两域全量,02 全套件——跨卡触面与关联档选测的盲区由全量兜底),结果落 `game/tests/.gates/ci-status.json`;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
## 5. 测试函数怎么写——验收清单条目的落码规范
+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/ 五个循环
+7 -7
View File
@@ -2,7 +2,7 @@
> **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。
> **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。
> **状态**:**已定稿(v1.20)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **状态**:**已定稿(v1.23)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **成文**:2026-10-13。
## 本文怎么用
@@ -103,8 +103,8 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 主菜单 | —— | 在役(迭代 33 卡②三入口化:进入存档/设置/退出游戏——入口拓扑=进入存档→选档界面→进档/新档→营地覆盖层→〔营地交互重做卡④〕营地场景图与生涯笔记本;2026-10-22 用户裁换代〔反哺 #13〕) |
| 开始覆盖层 | —— | 在役 |
| 选档界面(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。出口=五卡各自验收标准通过+营地屏用户目检通过) |
| 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](ux/camp.md) | 在役(迭代 47 卡④⑤ 落地——场景图+七物件点选+Esc 浮窗与顶栏,2026-11-01 合回) |
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md) | 在役(五卡全部落地——卡①②③ 迭代 33 批合回、卡④⑤ 迭代 47 批合回,卡⑥ 句柄子容器为同批前置基建卡。设计过程件已按吸收即死退役,沿革查 git 提交史。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 无玩家可见改动未登记) |
---
@@ -147,7 +147,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 |
| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史,复核转正归战斗一屏样张) |
| 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.md) | 待做(开工前置=先做素材轴/UI 轴对照截图定性,再按定轴修复;旧卡 v1.4 存 reports/ 作素材、非已写卡状态,重启须重新立卡;出口=用户实机目检第二、三关不再判「看着费劲」) |
| 立绘清晰度回退评估(是否改用高清图) | [角色设计](../art/character-design.md) | 待做(评估已结——2026-10-14 用户裁维持像素风、升级精细手绘像素、不回退高清图,裁决录=[美术审计-cursor会话存档](../../reports/美术审计-cursor会话存档-2026-10-14.md) §2.2;Alpha 范围=主角+三个 Boss 按新口径重产,其余敌种当前可读则保留到 Beta;出口=用户目检八敌体量关系通过且主角与三个 Boss 重产完成) |
| 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 待做(评估已结——不回退高清、维持像素风;终态方案=保留原稿分辨率、入包禁降采样、显示按三档绝对高 96/144/188 缩放、现有形象即终稿;裁决录=待办清单本条+art-bible §8.2;出口=9 件按档落位+用户目检清晰度与同屏体量顺序通过) |
| 敌人立绘(全敌种) | [角色设计](../art/character-design.md) | 在役 |
| 主角立绘三态 | [角色设计](../art/character-design.md) | 在役 |
| 状态图标 | [美术圣经](../art/art-bible.md) | 在役 |
@@ -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 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) |
@@ -190,7 +190,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 遗物式构筑协同 | [05-不做清单](05-不做清单.md) |
| 战后自动回复 | [05-不做清单](05-不做清单.md) |
| 反雪球机制 | [05-不做清单](05-不做清单.md) |
| 键盘 / 手柄输入 | [待办清单](../../待办清单.md)(排期归 Beta) |
| 键盘 / 手柄输入 | [待办清单](../../待办清单.md)(排期归 Beta。例外:营地语境 Esc 浮窗已落地〔迭代 47 卡⑤,用户明示裁决〕;键盘面其余仍排 Beta) |
| 数值统一标定(M4 数值批) | 2026-10-09 里程碑立规:M4 = Beta 阶段、M3 未由用户明令关闭前不排期(用户 2026-10-07 令移出迭代 18;2026-10-16 确认此裁长期有效)——待办清单各条「已编入 Alpha 收官 M4」措辞系旧口径残留 |
| 连刷 / 换图资金跨战斗携带 | [05-不做清单](05-不做清单.md) |
| 战斗节点地图类型差异化(更多地图主题) | 排 Beta 版本([playnote](../../playnote.md),2026-10-13 裁) |
+4 -4
View File
@@ -11,13 +11,13 @@
>
> **兑付频率(先看这条,它是三条渠道的共同前提)**:奇物**不是打一场战斗给一份**。关内的普通战斗节点打完了什么也不给;一关之内奇物**只兑付一次**,触发点是**关底 Boss 被击败**——而按 [冒险结构](冒险结构.md) 的定义,打掉关底 Boss 恰好就等于"通过这一关",这两件事是同一个时刻,不是一条链上的两步。
>
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地);现行为 v1.3。本文件是奇物的唯一定义文档。
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地);现行为 v1.4。本文件是奇物的唯一定义文档。
>
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `data/tables/feats.tres` 与 `data/tables/adventure.tres`。
## 专长(重设计·基础框架 v1)
> 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已实装:`feats.tres` 30 行=基础 27+迁入 2+规则豁免 1,行为与数据行 1:1。**
> 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已实装:`feats.tres` 37 行=基础 27+迁入 2+规则豁免 1+换图系 7(换图系七件 2026-11-01 迭代 46 落表),行为与数据行 1:1。**
### 设计原则
@@ -97,7 +97,7 @@
|---|---|---|
| 血税 | 本局:踩雷 −2 最大生命;标中 +1 攻击(**均下一战起生效**——战末汇入冒险加算区) | 双向都痛都爽,一句话讲得清;双向同账、本局累积、冒险收束清零。生命上限/攻击系属战界重建属性,即时改会被重建冲掉,故滞后一战兑付(2026-10-12 追认⑨回写;卡面 desc 同步补「下一战起生效」) |
### 换图系(7 件,设计定稿,待实现)
### 换图系(7 件,已实装——2026-11-01 迭代 46 落表 `feats.tres`,稀有度 14/13/10/0、每件持有上限 1)
换图系把「弃图刷新」从坏图逃生改成主动构筑行为。两件基座分别解决 RAP 的来源与价格;后续专长让成功刷新产生输出、生存或 GP 资源收益。七件不要求一次拿齐,不设强制先拿基座、集件标签或集件奖励。
@@ -117,7 +117,7 @@
- **成功才触发**:后续五件只认成功换图;刷新被拒绝时不产生事实,也不给输出、生存或资源收益。同一轮多次成功刷新按发生顺序逐次登记。
- **轮末兑付**:刷新不新增结算段。崩壁冲击走本轮战斗段末结账,石层缓冲在每次主角受伤时消费,坑道小憩与矿道红利走本轮授予段兑付。
- **全局受伤面**:石层缓冲覆盖敌方攻击、危险格、错误封印资源格与地形造成的主角伤害;临时生命值不在本流派设计范围内。
- **基线不变**:没有这些专长时,RAP 来源、刷新成本、清图奖励与伤害结果保持原规则。数值与稀有度归 `feats.tres`,落表前本节只作设计声明。
- **基线不变**:没有这些专长时,RAP 来源、刷新成本、清图奖励与伤害结果保持原规则。数值与稀有度归 `feats.tres`(已落表——稀有度档 14/13/10/0、每件持有上限 1;数值全 provisional 归调参批)。
### 迁入登记(秘宝域迁出件;实装批落地后并入件表)
@@ -1,7 +1,7 @@
# 地图战斗资源
> 一句话:格面资源的产出、极性字典与清图奖励封装
> 状态:已定稿(自旧体系迁移);现行为 v1.3
> 状态:已定稿(自旧体系迁移);现行为 v1.4
## 设计目的
@@ -147,7 +147,7 @@
### 清图奖励对象:整图封装,可配置可替换
清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格开完)时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。
清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格开完)时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。清图奖励只在清图批兑付:弃图刷新不触发清图事件,换来的新图不发清图奖励;刷新专长的固定 GP 收益走战斗内缓冲入账,不属清图奖励封装。
它是可配置可替换的封装:数据面上是清图池里的一行(现行默认槽 clear_default),换行即换内容,机制零改动。按图类型差异化已留结构位——`clear_<图类型>` 命名空间预留,不同地图类型可以配不同的清图奖励;词表现行 = {dungeon, underdark, deep_underdark}(2026-09-19 定稿两值、第三关内容批 2026-09-20 增 deep_underdark,玩家侧「地图类型」),差异化内容启用随内容批。
@@ -1,7 +1,7 @@
# 战斗结算
> 一句话:轮末结算的编排与逐行兑现
> 状态:已定稿(自旧体系迁移);现行为 v1.4
> 状态:已定稿(自旧体系迁移);现行为 v1.5
## 设计目的
@@ -266,9 +266,9 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
挖掘期刷新是玩家在挖掘中途主动换图,经[行动经济](../排雷循环/行动经济.md)扣费:扣费成功后按"旧板退役、生成新板、装板、资源预分配恰一次、变异布置"编排,本轮不打断——没有结算段、没有轮次结束。储备不足被拒是玩家决策的合法结果,不是编排错误。刷新图不发清图奖励,完美封印计数随装板重置。
### 换图系专长:刷新事实与本轮兑付(设计定稿,待实现)
### 换图系专长:刷新事实与本轮兑付(已实装——2026-11-01 迭代 46)
持有换图系后续专长时,成功刷新除执行上述换图编排外,还产生一次 `board_refreshed` 事实。事实只在新板装板成功后登记;刷新被拒绝时不产生事实,任何后续效果都不触发。同一轮多次成功刷新按发生顺序逐次登记,不合并成一次。
持有换图系后续专长时,成功刷新除执行上述换图编排外,还产生一次 `board_refreshed` 事实——它是换图系效果兑付的统一事实挂点。事实只在新板装板成功后登记恰一次;刷新被拒绝时不产生事实,任何后续效果都不触发。同一轮多次成功刷新按发生顺序逐次登记,不合并成一次。
刷新只登记事实,不当场插入结算。各效果在既有五段与对应消费点兑付:
@@ -280,7 +280,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
| 坑道小憩 | 每次刷新事实累计 1 点恢复量,最多 3 点;本轮授予段合计回复生命,封顶生命上限,随后清零;满血时记未生效行 |
| 矿道红利 | 每次刷新事实固定获得 1 GP,走战斗内 GP 缓冲并在授予段留痕;不按旧图探索进度折算,也不与清图奖励绑定 |
全局受伤事件统一:`damage_taken` 是主角受到伤害的唯一效果事件,覆盖敌方攻击、危险格、错误封印资源格与地形造成的主角伤害。石层缓冲订阅该事件并在现行伤害下限之前消费;旧有订阅同一事件的专长也按这一完整语义生效。临时生命值不在此机制内。
全局受伤事件统一:`damage_taken` 是主角受到伤害的唯一效果事件——敌方攻击、危险格、错误封印资源直伤与地形四条伤害路径统一经同一发射口发出、同一消费族消费,对该事件的全部既有订阅(减免、护甲修正、反击攒点、护盾吸收)对四条路径同等生效。石层缓冲订阅该事件并在现行伤害下限之前消费。临时生命值不在换图系流派设计范围内。
崩壁冲击若击杀最后敌人,仍由本轮判定段按既有顺序判胜,不新增挖掘期即时胜利出口。换图系效果使用扩展行留痕:崩壁冲击走伤害单据,石层缓冲在受伤消费行体现减免,坑道小憩与矿道红利分别走治疗与 GP 入账记录。刷新事实来源不指向单一棋盘格,使用明细流水的专长刷新效果来源豁免,不伪造来源格。
@@ -1,7 +1,7 @@
# 行动经济
> 一句话:每轮行动点怎么花、停手与刷新的代价
> 状态:已定稿(自旧体系迁移);现行为 v1.3
> 状态:已定稿(自旧体系迁移);现行为 v1.4
## 设计目的
@@ -91,7 +91,7 @@
停手与刷新合起来构成这本账的基线节拍:满行动点一轮停手,正好攒够一次刷新。行动点修正队列会扰动该节拍的实际兑现(+1 轮可多挖一格),不变式本身不动;对账归曲线调参批。
### 换图系专长对储备与刷新的改写(设计定稿,待实现)
### 换图系专长对储备与刷新的改写(已实装——2026-11-01 迭代 46 落表 `feats.tres`)
换图系两件基座分别改写 RAP 的来源与刷新价格。它们只给持有专长的玩家增加规则,不改变无专长时的基线账本。
@@ -100,7 +100,7 @@
| 矿脉储能 | 每次付费挖格被受理后,额外获得表值 RAP;安全格与危险格同口径 | 连锁展开只随首格的一次付费动作结算,不逐格追加;封印、移动、刷新不产 RAP |
| 省力周转 | 弃图刷新的 RAP 成本为基线的 50% | 只改价格;AP、轮次、每轮刷新上限与换图流程不变 |
两件基座可以同时持有并叠加:付费挖格照常产 RAP,刷新同时按半价扣费。两条规则作用在不同账户入口,不互相覆盖,也不设互斥。刷新仍须储备达到折后门槛才受理;储备不足或超过每轮次数上限时拒绝,状态不变。RAP 仍在战斗结束清零,刷新本身不返 RAP。
两件基座可以同时持有并叠加:付费挖格照常产 RAP,刷新同时按半价扣费。两条规则作用在不同账户入口,不互相覆盖,也不设互斥。折后价向下取整,受理门槛判断与实际扣费使用同一个折后值,只改价格不改拒绝判据——刷新仍须储备达到折后门槛才受理;储备不足或超过每轮次数上限时拒绝,状态不变。RAP 仍在战斗结束清零,刷新本身不返 RAP。
无矿脉储能时,挖格不新增 RAP;无省力周转时,刷新成本仍读全局基值。后续输出、生存与资源专长如何使用成功刷新这个事实,归[战斗结算](../战斗循环/战斗结算.md)。
@@ -249,8 +249,8 @@
| move_ap_cost | int | 表值(冻结 0) | 移动免费——冻结,非调参项 |
| chain_ap_cost | int | 表值(冻结 0) | 连锁免费——冻结,非调参项 |
| refresh_cap_per_round | int | 表值(provisional,结构位预留) | 每轮刷新上限,开轮重置计数 |
| (跨表)矿脉储能产 RAP | int [1, 64] | 设计定稿,待入 `feats.tres` | 每次受理的付费挖格兑付一次;连锁不逐格追加 |
| (跨表)省力周转成本率 | percent | 50%(结构已裁,待入 `feats.tres`) | 只改持有者的刷新价格;双基座叠加时不覆盖矿脉储能 |
| (跨表)矿脉储能产 RAP | int [1, 64] | 表值(已入 `feats.tres`,provisional 归调参批) | 每次受理的付费挖格兑付一次;连锁不逐格追加 |
| (跨表)省力周转成本率 | percent | 表值 50%(已入 `feats.tres`) | 只改持有者的刷新价格;双基座叠加时不覆盖矿脉储能 |
| (跨表)跨冒险行动点加成 | int [0,10] / int [0,99],冒险表 | adventure_table bonus_ap_flat / bonus_cap_ap(provisional) | 三选一 AP 件兑现值与全冒险次数上限;加成轴语义见「跨冒险行动点加成轴」节 |
字段名注记:2026-09-18 措辞完成批已全链对齐正式名——表字段与本文行名统一为 `refresh_cap_per_round`,刷新入口命名与表字段口径对齐(命名统一登记兑现,切片临时名零残留)。
+5 -4
View File
@@ -1,7 +1,7 @@
# 生涯
> **一句话定位**:营地里与三柜台平级的**局外只读界面**——营地管「花」(花钥石买永久强化),生涯管「看」(看自己跨局攒下的一切:累计战绩、历史冒险、图鉴收集)。冒险进行中不给任何数据回看(2026-10-04 用户裁决 P-C7 的延续),生涯是历史数据唯一的家。
> **状态**:期一已实装(2026-10-07 迭代 18 卡④生涯界面实装批·期一——跨局汇总页+主菜单入口+终身计数域存档 v6 落地;期二/期三另行立项,本文不预占排期)。入口拓扑已随迭代 33 卡② 三入口化换代、营地里经笔记本接入归迭代 34 卡④(信息架构节 v1.1,2026-10-22 反哺写入)。现行为 v1.3。
> **状态**:期一已实装(2026-10-07 迭代 18 卡④生涯界面实装批·期一——跨局汇总页+主菜单入口+终身计数域存档 v6 落地;期二/期三另行立项,本文不预占排期)。入口拓扑已随迭代 33 卡② 三入口化换代、营地里经笔记本接入归迭代 34 卡④(信息架构节 v1.1,2026-10-22 反哺写入)。现行为 v1.4。
> **来源**:[待办清单「『生涯』界面——冒险中数据回看的唯一承接处」条](../../../../待办清单.md)(2026-10-04 用户裁决 P-C7——冒险中不给数据回看,历史战报与结算明细的查看去向 = 新「生涯」界面)。
## 一、信息架构
@@ -69,7 +69,7 @@
| 界面 | 管什么(现状指证) | 与生涯的边界 |
|---|---|---|
| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树)+ 底栏「最近冒险」条带——营地底栏把摘要逐局拼行(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」)、无截断上限,数据源与生涯历史回看同属一份最近摘要 | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带的去留是本提纲必答项——见 §五 P6 |
| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树——2026-11-01 迭代 47 卡④ 起为营地场景图+五功能窗口形态);原底栏「最近冒险」条带已随同批撤下(P6 甲提前落地),逐局历史回看唯一归生涯 | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带已撤(P6 甲——随迭代 47 卡④ 执行) |
| 状态面板(迭代 17 卡③,并行批开发中) | **局内**只读查看这一局进行中的钥石与本局冒险进度 | 状态面板看当局当前值,生涯看跨局累计与历史;局内无生涯入口(P4 甲) |
| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 |
@@ -82,7 +82,7 @@
| 期 | 内容 | 前置 | 建议窗口(候选) |
|---|---|---|---|
| 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 schema 迁移——**✅ 已实装(2026-10-07 迭代 18 卡④)**:汇总段六类九项+营地与系统组生涯钮+存档 v6 两终身计数域〔v5 升 v6 幂等迁移〕;闸读数与代拍板追认单原记于该批出口报告,该件已按「吸收即死」删除,读数以本行与该批提交信息为准 | 提纲评审通过。实装批前置义务 = 对齐迭代 16 UI 批后的现行基线:字号六档 / 语义色 / 按钮最小高 40 点等现行界面基线 / 色板单源 / 页面壳的模态栈纪律——与卡③需求卡片同款红线,本文只登记、不展开 | Alpha 后最近一个 UI 窗口 |
| 期二 | 历史冒险回看整段:N 扩容 + 用时埋点 + 旧档摘要迁移 + 逐局列表 + 单局明细(遗留明细界面复活承接)+ 营地条带处置(P6 裁案落地) | 期一。实装批开工前须向用户呈报两件:N 终值读数(20 / 50 / 维持 10 三选一)与「用时」埋点口径(计时起点、暂停是否计入——字段级细节本文不做) | 期一落地后独立批 |
| 期二 | 历史冒险回看整段:N 扩容 + 用时埋点 + 旧档摘要迁移 + 逐局列表 + 单局明细(遗留明细界面复活承接)——营地条带处置已随 P6 甲提前落地(2026-11-01 迭代 47 卡④ 撤下),期二无残留条带义务 | 期一。实装批开工前须向用户呈报两件:N 终值读数(20 / 50 / 维持 10 三选一)与「用时」埋点口径(计时起点、暂停是否计入——字段级细节本文不做) | 期一落地后独立批 |
| 期三 | 图鉴承接(敌人优先,范围与顺序随 P3 甲);「见过」记账面 schema 义务随该批执行 | 期二或独立评估 | Beta 窗口候选(与教学引导同段评估——图鉴的「认得」轴与教学相关;待办清单教学引导节各条 2026-10-04 起已整体标 Beta 阶段) |
本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装;文案权先例 = 教学文案定稿权登记于待办清单),实装批承接。
@@ -91,9 +91,10 @@
P0-P5 六点已于 2026-10-06 用户全裁(案甲 × 6,备选案与淘汰依据在需求卡片 §8),结论已写入上文对应节,不重复列。本批新发现的待裁点一项:
### P6 · 营地底栏「最近冒险」条带去留(期二必答项)
### P6 · 营地底栏「最近冒险」条带去留(已裁甲案并落地——2026-11-01 迭代 47 卡④)
**现状与问题**:营地底栏「最近冒险」条带把摘要逐行全部拼进同一个标签(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源与生涯历史回看同属一份最近摘要。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。
**裁决与执行**:用户裁甲案(2026-11-01,随迭代 47 卡④ 四柜台拆解执行)——条带已撤下、逐局历史回看唯一归生涯;期二生涯历史回看批的条带处置义务随之清空。
| 案 | 内容 | 淘汰/保留依据 |
|---|---|---|
+3 -3
View File
@@ -2,7 +2,7 @@
> **Status**: Complete(现行)
> **Author**: user + ux-designer
> **Last Updated**: 2026-10-29
> **Last Updated**: 2026-11-01
> **Platform Target**: PC (Steam)——Keyboard/Mouse 主(全部操作纯鼠标可完成)、Gamepad Partial 非 MVP、Touch None(同 technical-preferences)
> **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算的挖掘期刷新编排 + 恒同盘不变、系统索引的刷新规则同步——见 OQ11)
> **Template**: UX Spec
@@ -141,7 +141,7 @@
```
- N = 棋盘长宽两边的较大者;格间距 2px;垂直预留 24px(上 12 / 下 12);悬崖厚度 8px;结构钳制区间 [26, 46]px。
- 可读下限 30px——与结构钳制分开:钳制管算式不越界,下限管玩家能否看清。
- 格边长可读性以用户验收的现行画面为锚点(2026-11-01 用户裁——现行渲染值即基准,基座档 14×14=28px 达标);全域不设人造的格边长可读阈值。结构钳制 [26, 46] 只管算式结果不越界;格边长接线守卫=生产写入值与上式单点算式的返回值严格相等,最大板型的推定读数仅留痕观察、不作判红依据。
- N 的取值与每关板型的对应关系读[棋盘生成](../systems/排雷循环/棋盘生成.md),现行值的事实源是 `data/tables/board_gen.tres`。
- 数字字号 = max(18, ceil(格边长 × 0.5)),像素字体整数倍缩放(canvas_items stretch 保证清晰)。
@@ -366,7 +366,7 @@
- [ ] 两种格态(已开格 / 未开格)灰度截图下一眼可辨(排雷玩法×6②)
- [ ] 悬停光标区分移动/挖/封印三语义;可挖域(8 向相邻)常驻指示;非相邻格悬停显禁止反馈(排雷玩法×6③)
- [ ] 已引爆危险格呈"可通行可读"(排雷玩法×6④)
- [ ] 全部数字对所在格底对比 ≥4.5:1;明度分层规则无违例(art-bible §2/§4.1,v3.0——状态区分不走明度,格底功能分档合法)
- [ ] 全部数字对所在格底对比 ≥4.5:1;明度分层规则无违例(art-bible §2/§4.1——格底功能分档合法,格态语义由形与色相承载)
**交互与反馈**
+4 -4
View File
@@ -1,6 +1,6 @@
# 营地场景 UX spec(camp)
> 状态:v2.2(现行)
> 状态:v2.4(现行)
> 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制)
> 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.md)
> 相邻:[level-map.md](level-map.md)(主菜单语境)
@@ -28,7 +28,7 @@
**中景带自画面底部往上约 2/3 处起**;近景物件允许压住围墙(围墙不可点,z 序在近景物件之下),**唯一例外=中间门洞必须留空、不被任何物件遮挡**。围墙形态、门扇比例与帐篷尺寸的现行口径归 [art-bible](../../art/art-bible.md) §6.2,本 spec 不再复述。
坐标草案与分层验收判据见 [camp-scene-prompts](../../art/camp-scene-prompts.md) §2.3/§4(10 张口径现行事实源);2026-10-22 美术方案讨论留痕见 [提案](../../../reports/提案-营地场景图美术生成方案-2026-10-22.md)。
坐标草案与分层验收判据见 [camp-scene-prompts](../../art/camp-scene-prompts.md) §2.3/§4(10 张口径现行事实源);2026-10-22 美术方案讨论沿革查 git 提交史。
## 点选反馈(根态)
@@ -38,11 +38,11 @@
## 顶栏
钥石余额常驻画面顶部一行小 HUD 栏(高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**)。刷新入口=营地刷新单口(卡⑤同口挂接)。
钥石余额常驻画面顶部一行小 HUD 栏(高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**)。刷新入口已实装=营地刷新单口(2026-11-01 迭代 47 卡④⑤:进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。
## 叠层与开合
各界面开合走模态栈单口,层序按模态栈契约登记。**承载形态(独立全屏页 vs 单一内页切换)与叠层规则归卡④ §9 代拍板域,本 spec 不预设值**;派生规则:独立全屏页=场景根隐藏/单一内页切换=场景根留底(「缩放虚化」为第三形态,禁批内自选)。ESC 浮窗(返回主菜单/设置/退出游戏)归卡⑤。
各界面开合走模态栈单口,层序按模态栈契约登记。**承载形态已定稿(2026-11-01 用户裁)**:五个功能界面(锻造工坊/秘宝匣/天赋树/奖励格图鉴/生涯记录)各以**功能窗口**打开——场景根留底可见,窗口之下垫半透明压暗遮罩(交互聚焦用,复用既有模态遮罩件,现值锚黑色 0.6;观感随样张目检校准,属普通设计决策)。**五窗互斥单开**(模态栈深恒 1,不开窗叠窗);各窗自带关闭钮。ESC 优先序:有功能窗口开着 → 先关最上层窗;无窗 → 才进 ESC 浮窗(返回主菜单/设置/退出游戏)。
## 口径
+1 -1
View File
@@ -234,6 +234,6 @@
| [HUD框架](../systems/表现与支撑/HUD框架.md) | 模态 overlay 层级纪律、订阅纪律(推进信号两线之一) | Navigation Position + Data Requirements 架构注记 |
| HUD框架 | 可访问性基线全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体) | Accessibility |
| [battle.md](battle.md) OQ14 二轮拍板 | GP = 关卡画面常驻总量 | 顶栏 GP 常驻徽章 |
| [art-bible](../../art/art-bible.md) | 方块地牢观感(§3.2 圆角方形世界侧)、色板冻结(§4.1)、半拟物(§7.1)、明度分层(§2,v3.0——状态不走明度、空间光照可走) | 图区组件 + Accessibility 对比条 |
| [art-bible](../../art/art-bible.md) | 方块地牢观感(§3.2 圆角方形世界侧)、色板冻结(§4.1)、半拟物(§7.1)、明度分层(§2) | 图区组件 + Accessibility 对比条 |
---
+29 -12
View File
@@ -93,11 +93,29 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
_reject("phase")
return false
var cost := _cost("dig")
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前:
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前——施加单点 _apply_dig_paid_hits):
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。
# counter_add〔双管镐 付费挖格计数〕/ grant_ap〔每第 2 次返 1 AP——对价减免,
# mod_eq 闸在行 when〕同面施加)
var apply_res := _apply_dig_paid_hits(cost)
cost = int(apply_res["cost"])
if _ap < cost:
_reject("resource_face")
return false # 拒绝轮零入池(矿脉储能只在扣费受理后兑付——C2 成功才登记同款纪律)
if int(apply_res["refund"]) > 0:
cost = maxi(0, cost - int(apply_res["refund"])) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
var accepted := _spend_cost_and_arbitrate(cost, clears_map)
if accepted and int(apply_res["rap_gain"]) > 0:
grant_reserve_ap(int(apply_res["rap_gain"])) # 每次受理的付费挖格产 RAP(一次性入池——每动作恰一挖,连锁不二次)
return accepted
# dig_paid 命中施加单点(迭代 46 卡A T3 自 request_dig 迁出——分支本体逐字保留:
# counter_spend 加收对价并积计数/counter_add 施加〔双管镐 付费挖格计数〕/
# grant_ap 对价减免〔每第 2 次返 1 AP——mod_eq 闸在行 when〕/grant_reserve_ap 矿脉储能累计;
# 命中序=声明序不变。返回 {cost, refund, rap_gain} 交受理面消费)
func _apply_dig_paid_hits(base_cost: int) -> Dictionary:
var cost := base_cost
var dig_refund := 0
var rap_gain := 0
if _fx != null:
for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
if String(h.get("kind", "")) == "counter_spend":
@@ -110,12 +128,9 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
EffectEngine.counter_add(_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
elif String(h.get("kind", "")) == "grant_ap":
dig_refund += int(h.get("value", 0))
if _ap < cost:
_reject("resource_face")
return false
if dig_refund > 0:
cost = maxi(0, cost - dig_refund) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
return _spend_cost_and_arbitrate(cost, clears_map)
elif String(h.get("kind", "")) == "grant_reserve_ap":
rap_gain += int(h.get("value", 0))
return {"cost": cost, "refund": dig_refund, "rap_gain": rap_gain}
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
@@ -196,13 +211,15 @@ func finish_round_stop(transfer: bool, transfer_pct: int = 100) -> void:
# 轮内刷新:唯一入口 =
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
func request_refresh() -> bool:
# cost_override=折后价入参(迭代 46 卡A T3——BattleRefreshFlow 折价读数传入;缺省 -1=表基线逐位不变)
func request_refresh(cost_override: int = -1) -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
if _rap < db.run.action_economy.rap_refresh_cost:
var cost := int(db.run.action_economy.rap_refresh_cost) if cost_override < 0 else cost_override
if _rap < cost:
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
_rap -= db.run.action_economy.rap_refresh_cost
_rap -= cost
return true
+9 -17
View File
@@ -31,8 +31,9 @@ signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
# fx_reveal=专长揭示掷格(落空行无真实格);
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指)
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp"]
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指);
# fx_board_refresh=成功刷新事实族(迭代 46 卡A——崩壁冲击群伤行源=换图动作而非棋盘格)
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp", "fx_board_refresh"]
var _facts: BattleFacts # 战斗事实件(battle_facts.gd——事实族 15 成员单点)
var _rounds: RoundCycle # 轮循环驱动件(settlement/round_cycle.gd——五段持有/段序/重入闸/摇号)
@@ -596,26 +597,17 @@ func _exit_distribution() -> void:
round_started.emit(round_index)
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
func can_refresh() -> bool:
return battle_result == "" \
and _facts.refresh_used < int(db.run.action_economy.refresh_cap_per_round) \
and action_economy.phase == action_economy.PHASE_ACTIVE \
and action_economy.rap >= int(db.run.action_economy.rap_refresh_cost)
return BattleRefreshFlow.can_refresh(self)
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
# 挖掘期唯一通道:Active 相位立即换图
# 继续挖(RAP -8;AP 保留、相位不动、不触发结算);非挖掘期请求一律拒绝——非清图轮恒同图。
# 挖掘期唯一通道:Active 相位立即换图继续挖(RAP 按折后价扣;AP 保留、相位不动、不触发结算);
# 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
func player_request_refresh() -> bool:
if not can_refresh():
return false
if not action_economy.request_refresh():
return false
_facts.refresh_used += 1
_reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
minesweeper.retire_board(true) # 旧板退役(轮内通道)
return _install_and_reset(true) # 当场换图——失败即编排违例 fail-fast
return BattleRefreshFlow.execute(self)
func _battle_over(result: String) -> void:
+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
+28 -10
View File
@@ -460,8 +460,8 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
# 掷点算术归 _enemy_roll;damage_taken 施加面归 _enemy_shield_apply(发射在本函数——
# 与减伤落快照的先后序固定)。
# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 _emit_hero_damage_taken(发射在本函数——
# 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。
func _resolve_enemy_skill(t: Dictionary) -> void:
var hero: Dictionary[Variant, Variant] = units.hero_unit()
var roll := _enemy_roll(t)
@@ -469,9 +469,8 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
# damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers),
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供)
var dt_hits := _fx_hits("damage_taken", {"kind": "enemy_skill", "amount": total,
"source": String(t.context.get("skill_id", "")), "round": int(t.round_index)})
total = int(_enemy_shield_apply(dt_hits, total)["total"])
var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
total = int(dt_res["total"])
units.apply_damage(hero, total)
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
"roll": int(roll["roll"]), "ac": int(roll["threshold"]), "tier": int(roll["discount"]), "attack_type": String(t.attack_type)}
@@ -502,9 +501,22 @@ func _enemy_roll(t: Dictionary) -> Dictionary:
return {"roll": int(roll_read.roll), "threshold": threshold, "crit": crit, "discount": discount, "total": total}
# damage_taken 施加面(shield 加法族合计;damage_reduce 逐条减量〔背水〕;ac_bonus 转编排器单点
# 〔石肤 AC 累积归 set_hero_ac_override 消费面〕;counter_add 施加)。返回 {total, shield}。
func _enemy_shield_apply(hits: Array, total: int) -> Dictionary:
# 全局受伤发射单点(迭代 46 卡A T2——所有主角承伤路径唯一发射口:敌方攻击/地形/危险格/错误封印
# 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
# 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。
# payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。
func _emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary:
var hu: Dictionary[Variant, Variant] = units.hero_unit()
var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source,
"cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))}
var hits := _fx_hits("damage_taken", payload)
return {"total": int(_apply_hero_damage_effects(hits, amount)["total"]), "hits": hits}
# damage_taken 唯一消费族(组合次序冻结:shield 加法族合计→damage_reduce 逐条减量〔背水/石层缓冲〕→
# ac_bonus 转编排器单点〔石肤 AC 累积归 set_hero_ac_override 消费面〕→counter_add 施加〔保险丝/石肤/
# 换图系临时计数夹具〕。返回 {total, shield})。
func _apply_hero_damage_effects(hits: Array, total: int) -> Dictionary:
var shield := 0
for h in hits:
match String(h.get("kind", "")):
@@ -565,9 +577,15 @@ func _resolve_board_blast(t: Dictionary) -> void:
_outcomes[int(t.ticket_id)] = "redeemed"
# 棋盘主角直伤(反噬/治疗包负向/地形变异):无类型必中,下限保底;context 键并入行载荷
# 棋盘主角直伤(反噬/治疗包负向/危险格/错误封印资源直伤):无类型必中,下限保底;context 键并入行载荷。
# damage_taken 全局发射点(迭代 46 卡A T2——单据未标 hero_damage_consumed 时在下限前发射并消费一次;
# 地形路径已在授予段开单点发射并标记,此处跳过防重复)
func _resolve_board_flat_hero(t: Dictionary) -> void:
var dealt: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
var dealt: int = int(t.base_value)
if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)):
var res_flat := _emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index))
dealt = int(res_flat["total"])
dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限)
units.apply_damage(units.hero_unit(), dealt)
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
for k in t.context.keys():
+12 -4
View File
@@ -12,11 +12,15 @@ extends RefCounted
const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commit", "clamp_check",
"armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle",
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed",
"board_installed"]
"board_installed",
# 换图系双事件(refresh_request=玩家请求刷新〔省力周转 apply_discount 订阅面——折扣只改门槛
# 与扣费不改拒绝判据〕;board_refreshed=成功刷新事实〔换图系七件订阅主挂点——装新板后才发射〕)
"refresh_request", "board_refreshed"]
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"]
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件+轮内刷新面〔换图系〕)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll",
"refresh_request", "board_refreshed"]
## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、
@@ -40,7 +44,11 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
"mine_count_preview",
# forgive_seal_miss=完美保险(本场首次封印落空不计入完美
# 封印判定——订阅 seal_missed 的豁免效果,消费点在授予段;feats.tres 首例 domain=battle 行)
"forgive_seal_miss"]
"forgive_seal_miss",
# 换图系两效果(grant_reserve_ap=矿脉储能·付费挖格产 RAP〔消费点在行动经济 dig_paid
# 受理后一次性入池〕;apply_discount=省力周转·刷新成本折扣〔mode=refresh cost_mult 百分比,
# floor 取整——只改门槛与扣费、不改拒绝判据〕)
"grant_reserve_ap", "apply_discount"]
## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
+6
View File
@@ -365,6 +365,12 @@ func outcome_of(ticket_id: int) -> String:
return "redeemed" if ticket_id < 0 else damage.outcome(ticket_id)
# 延迟单据入队(迭代 46 卡A T4——成功刷新的崩壁冲击群伤单走案C 同队列:授予段闸外唯一入口,
# 战斗段末 _board_deferred_settle 按开单序补提交统一结账;类变量零增员——复用 defer.queue)
func defer_ticket(t: Dictionary) -> void:
(defer.queue as Array).append(t)
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
func _submit_flat_hero(display: String, power: int, att: Dictionary, sync: bool) -> String:
var t := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, String(att.get("source_class", "")), display,
@@ -0,0 +1,43 @@
class_name BattleRefreshFlow
extends RefCounted
## 换图刷新流程件(迭代 46 卡A T3/T4 自编排器拆出):刷新可用性门、折价扣费、换图执行与成功事实登记。
## 拆分边界(需求卡片 C13):本件只负责可用性、扣费、换图与成功事实登记——七件效果的算法留在
## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→本件→行动经济/装板壳/授予段。
## 反自动刷新(C11):只经玩家入口发起,禁 RAP 满自动刷——编排器公开面单行转发,无自动调用点。
## 成功序钉死(T4):折价扣 RAP→refresh_used+1→旧资源退役→旧板退役→装新板→授予段成功事实发射;
## 装板失败返回 false 且零效果——成功事实只在新板就位后发射。
## 折价成本读数(refresh_request 命中省力周转 apply_discount——只认 kind==apply_discount 且
## mode==refresh,取声明序第一命中,floor(base×cost_mult/100);无命中=表基线逐位不变。
## can_refresh 与 execute 同源取数〔V3——can 与执行同源判断〕;折扣只改变门槛与扣费,
## 不改变拒绝判据〔C2——每轮上限与相位判据照旧〕)
static func _effective_refresh_cost(oc: BattleOrchestrator) -> int:
var base := int(oc.db.run.action_economy.rap_refresh_cost)
for h in oc.fx_host().fx_emit("refresh_request", {"mode": "refresh", "base": base}):
if String(h.get("kind", "")) == "apply_discount" and String(h.get("mode", "")) == "refresh":
return int(floor(float(base) * float(int(h.get("cost_mult", 100))) / 100.0))
return base
## 可用性门(HUD 刷新按钮同源判据):战斗未终局、每轮上限未满、Active 相位、RAP 抵折后价。
static func can_refresh(oc: BattleOrchestrator) -> bool:
return oc.battle_result == "" \
and oc._facts.refresh_used < int(oc.db.run.action_economy.refresh_cap_per_round) \
and oc.action_economy.phase == oc.action_economy.PHASE_ACTIVE \
and oc.action_economy.rap >= _effective_refresh_cost(oc)
## 执行(玩家请求刷新唯一通道体):扣费受理后继续;扣费拒绝/门拒零效果零事实。
static func execute(oc: BattleOrchestrator) -> bool:
if not can_refresh(oc):
return false
if not oc.action_economy.request_refresh(_effective_refresh_cost(oc)):
return false
oc._facts.refresh_used += 1
oc._reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
oc.minesweeper.retire_board(true) # 旧板退役(轮内通道)
if not oc._install_and_reset(true): # 当场换图——失败即编排违例 fail-fast(零成功事实)
return false
oc._rounds._grant_seg.apply_board_refreshed(oc.round_index) # 成功事实:新板就位后一次发射
return true
@@ -0,0 +1 @@
uid://d2kjxlt5b2eqv
+50 -24
View File
@@ -6,7 +6,8 @@ extends RefCounted
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_terrain_damage_reduce/_seal_hit_effects 等单面函数);
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken
# 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数);
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
@@ -32,6 +33,27 @@ func run(ri: int) -> void:
_round_end_apply(ri)
# 成功刷新事实发射+施加(迭代 46 卡A T4——BattleRefreshFlow 装新板后单点调用,每成功刷新恰一次;
# 拒绝刷新零事实〔C2〕。命中消费三面:counter_add→既有单点落账〔蓄力周转/石层缓冲/坑道小憩 计数〕;
# grant_gp→战斗内缓冲 escrow 入账+fx_grant_gp 行〔矿道红利——不绑清图奖励,C7〕;
# open_ticket channel=blast→崩壁冲击群伤单延迟入队〔案C 同队列,战斗段末按存活目标结账〕)
func apply_board_refreshed(ri: int) -> void:
var br_hits := fx_host.fx_emit("board_refreshed", {"round": ri})
for h in br_hits:
if String(h.get("kind", "")) == "counter_add":
_apply_counter_hit(h)
for h in br_hits:
if String(h.get("kind", "")) == "grant_gp":
reward_grant.escrow += int(h.get("value", 0))
oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow})
for h in br_hits:
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
var bb_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击",
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
"ri": ri, "origin_cell": Vector2i(-1, -1)})
reward_grant.defer_ticket(bb_t)
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
func _reveal_batch(ri: int) -> void:
for evt in oc._facts.reveals:
@@ -167,37 +189,22 @@ func _terrain_batch(ri: int) -> void:
if String(vrow.get("effect", "")) != "flat_damage":
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
# damage_taken 发射在单据开立前:damage_reduce〔背水——hp_ratio when
# 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化)
# damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken:
# 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
# hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
var dt_amount := int(vrow.get("effect_value", 0))
var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount,
"source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid),
"hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))})
dt_amount = _terrain_damage_reduce(dt_hits, dt_amount)
var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
dt_amount = int(dt_res["total"])
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid)}})
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}})
reward_grant.submit(vt, false)
for h in dt_hits:
for h in (dt_res["hits"] as Array):
if String(h.get("kind", "")) == "grant_material":
_apply_material_hit(h, evt.cell, ri)
# damage_taken 施加面(damage_reduce 减量收集〔背水——定值〕/ac_bonus〔石肤〕/counter_add〔保险丝/石肤〕;
# 返回减后 amount 交开单消费)
func _terrain_damage_reduce(dt_hits: Array, dt_amount: int) -> int:
for h in dt_hits:
match String(h.get("kind", "")):
"damage_reduce":
dt_amount = maxi(0, dt_amount - int(h.get("value", 0)))
"ac_bonus":
fx_host.apply_ac_bonus(h)
"counter_add":
_apply_counter_hit(h)
return dt_amount
# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
@@ -259,6 +266,7 @@ func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionar
func _round_end_apply(ri: int) -> void:
var re_fx := fx_host.fx_emit("round_end", {"round": ri})
_round_end_blast_tickets(re_fx, ri)
_round_end_heal_value_counter(re_fx, ri) # 迭代 46 卡A T5——坑道小憩恢复兑付(先于清零行消费层值,声明序=heal 行在 counter set 行前)
for h in re_fx:
if String(h.get("kind", "")) == "counter_add":
_apply_counter_hit(h)
@@ -278,6 +286,24 @@ func _round_end_apply(ri: int) -> void:
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点,
# units.heal 封顶、满血发未生效行;层清零由同事件 counter_add set 行承载〔声明序在后〕——本面
# 先于 counter_add 遍历消费层值即保序。行源=成功刷新事实族(无棋盘格——fx_board_refresh 豁免类))
func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void:
for h in re_fx:
if String(h.get("kind", "")) != "heal" or String(h.get("value_counter", "")) == "":
continue
var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", "")))
if layers <= 0:
continue
var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)):
oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))})
continue
var healed: int = oc._enemy_system.heal(hu, layers)
oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"})
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付)
func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
@@ -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
+15
View File
@@ -11,3 +11,18 @@ rap_refresh_cost = 8
refresh_cap_per_round = 2
move_ap_cost = 0
chain_ap_cost = 0
; AP 压速批(2026-09-16 用户拍板):ap_per_round 8→6——无伤通关根因诊断(playtest_5678/1234 实录:
; 第 1 轮 8 AP 全封印 = 8 份正向兑付 + 16 魔法充能,减伤/削弱单轮堆满 → "被减伤归零"零承伤通关)
; 的主阀门:削每轮兑付吞吐(封印 rush 墙/归零轮覆盖率 ~50%→~35%)。单格兑付语义零改动(保留封印命中
; 正向兑付——危险格/安全格概率差异化设计的前提)。rap_refresh_cost 8→6 随动 = 保持 registry 登记
; "默认 = ap_per_round,满停一轮 = 恰好一刷基线节拍"耦合不变式;若有意解耦(刷新相对变贵推清图)
; 须单独立批。减伤幅度下调(-30 → 下调)为同根因配套项,待用户拍板数值后另行落表;
; 终局方案 = 减伤换装护甲等级 AC+1(随 AC 掷点定稿批 EV 中性换装)。
; 观察口径:AP 6 不够再降 4(墙覆盖率 ~19%,代价 = 12×12 板挖掘节奏拖长)。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.2):ap_per_round 6→8、
; rap_refresh_cost 6→8 随动(维持 registry 登记"刷新成本 = 每轮 AP,满停一轮恰一刷"不变式;schema
; 非硬约束,系设计不变式)。上批 8→6 所杀"首轮全封印→减伤削弱堆满→零承伤通关"退化,在 B1 敌人 HP
; 大幅抬升后单轮堆满已无法零承伤通关多轮整场,风险缓解;复测须盯 analyze.py 零承伤轮指标,明显回升
; 则按计划书 §6.5 AP 8→7 回调。dig/seal/move/chain cost 与 refresh_cap 未动(move/chain 结构冻结 0)。
+45
View File
@@ -408,3 +408,48 @@ rows = [{
"shave_ring_radius": 1,
"r0_max_cap": 59
}]
; 切片调参登记(SLICE-TUNED provisional,调参所有权仍归 C3):
; A1 GDD 参考行(d0.10/0.12)经"先布后删"环内移除后仅幸存 2-3 雷、R0/|S|约0.97、残余挖掘 2-4 格——
; 掘近退化板(A2 反例 B 机制的低密度放大)。实验数据(8 种子均值,smoke/experiment_density.gd):
; d0.25 → L1 幸存雷 11/残余 13/inf 0.925/att 2.9 · L2 雷 19/残余 21/inf 0.908/att 7.9(seed42 实测 inf 0.889→target 定 0.88 首过),零降级告警。
; target 收窄至 0.88/0.88(不低于 0.85 绝对锚)为密度上移的对价——B1 条款③⑤对账重跑义务随调参触发。
; 2026-09-11 关卡扩展批:L1 增 D1–D4 行(Boss=Dmax=4)。
; Boss 板以 12×12 区分(d0.28/t0.87 实测达不到推理率门槛——best≈0.81 降级告警,回退 0.25/0.88;
; 密度轴上调留给 C3 与求解器能力一起解)。
;
; 分支节点图批(2026-09-17——L2 幽暗地域板行铺满,全值 provisional 归 难度曲线与Boss):
; L2 战斗/Boss 节点 (2, 0/1/4/5/6/7/8) 七行精确铺满(跨表校验强制——ConfigDB ④ level_node_dangling);
; 奖励点 (2,2)/恢复点 (2,3) 不开战斗、无板行(跨表校验只查 battle/boss 节点)。
; 板面参数与 L1 高档同值(12×12 d0.25 t0.88)——密度/推理率轴差异化留给 难度曲线与Boss 调参批;
; board_type 词表 v1.0(2026-09-19 用户拍板):L1=dungeon 地牢基线 / L2=underdark 幽暗地域(玩家侧显示名「地图类型」);域校验在 board_gen_table.gd 词表常量。
;
; 第三关内容批(2026-09-20):板面 13×13 / Boss 14×14 兑现「Boss 板 = 本关最大板形」纪律;
; 密度维持 0.25(0.28 破推理率门槛有失败先例——切片调参登记 best≈0.81 降级告警,加难主杠杆 = 板面 + 地形);
; 词表第三值 deep_underdark(灰矮人矿道方向——v1.1 2026-09-20 L3 内容批,显示名归呈现侧);
; min_safe_count 按既有 ~0.42 格占比外推(13×13 → 70 / 14×14 → 82)。
; 降档登记:Boss max_gen_attempts 批内拟值 36 越 schema 域 [1,32](config_fatal 修行值纪律——不许放宽 schema),
; 按域上限取 32(批内降档梯子「Boss 回 13×13 时 attempts 32」同款值)。行值全 provisional 归 难度曲线与Boss。
;
; 商店房批连带(2026-09-21——level.tres 商店房批 Boss 重编号 8→9):新增 (2,9)/(3,9) 精确行
; (跨表校验强制——ConfigDB ④ level_node_dangling),行值照抄原 Boss (L,8) 行;
; 商店房 (L,8) 不开战斗、无板行(跨表校验只查 battle/boss 节点)。行值 provisional 归 难度曲线与Boss。
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 板行照抄到 (2,8)/(3,8)——节点 8 现为
; 商店房、无板行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
;
; Package B 迭代·第 6 轮·d3 改板试点(2026-09-22——用户提出并授权,破 §4.4 板面冻结的单行试点):
; (1,3) 第一关·节点3·熊地精双雄 板 12×12→10×10(min_safe_count 60→50、max_gen_attempts 24→16,
; 对齐 (1,0)/(1,1) 的 10×10 行)。依据:第 5 轮读数 d3 完成度 84% 中 83.2pp 系 R₀ 免费展开、实挖仅
; ~18 格/3.7 轮——总血 28-42 撑不起 121 格精英板;缩板后同战斗量读数 (68.6+18)/87≈100% 回带,
; 清图率升(打完近全翻开,清图尖峰 24-40 伤可兑现),轮数/承伤/胜率预计不动(难度刀≠设计意图修复)。
; 连带义务:board_calibration.csv 全量重跑 calibrate_boards(§4.4 预告)。provisional 未提交。
;
; 板尺寸策略改判批(2026-09-24——用户拍板「小怪板统一 10×10」原则,取代第 6 轮「板随战斗时长」原则):
; 第 6 轮单行缩板在 L1 内造出 10→10→12→10→12 锯齿——弱怪长战配大板、强怪速战配小板,节奏倒挂
; (用户实测定罪:第三场单只熊地精 50 血占 12×12、第四场三只强怪占 10×10)。本批 L1 小怪场统一 10×10:
; d2 12×12→10×10(min_safe_count 60→50、max_gen_attempts 24→16,对齐本关其余小怪行);
; **boss d4 维持 12×12 不动**(首次实施时误将 boss 板一并缩小,用户纠正后回滚——boss 场更大=
; 战斗逐渐变难的视觉收束,板序列 10/10/10/10/12 单调递增)。
; 战斗轮数由怪血量与玩家输出决定、与板大小无关=难度中性;完成度分母/清图率变化连带 board_calibration
; 全量重跑。L2 全 12×12 / L3 全 13×13(boss 14×14 单调收尾)本原则下无需动。provisional 未提交。
+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")]
+2
View File
@@ -5,3 +5,5 @@
[resource]
script = ExtResource("1")
rows = []
; C3 GDD Core Rule 1:全局修饰结构位——MVP 空表(零行为)。首个修饰行 = 精英节点上线(C1 联动)。
+172
View File
@@ -754,4 +754,176 @@ rows = [{
"armor_class": 12
}]
; 切片调参登记 v3(2026-09-12 试玩批次 1 触发——F7 流程;C3 GDD F5 对照):
; 试玩反馈 = 基线满血全冒险成功(模拟 80%/真人 100%)——难度严重不足,主杠杆 = atk 曲线上调(承伤侧):
; 4/4/6/4/7 → 7/5/9/6/9(约 +50%);HP 结构调整(D0 24→20 缩短战斗、Boss 130→150 用户拍板可上调)。
; D0 注:20 HP 会被首轮盲挖粉尘秒杀(12+9)——回 24(4 轮战斗/承伤 42%,有过程有痛感)。
; DND 命名(用户指令):goblin 地精(单拍杂鱼)→ bugbear 熊地精(三拍伏击环——DND 原型即伏击手)→
; ogre_chief 食人魔酋长(经典低阶 Boss)。enemy_id 同步更名(内部键,无外部引用)。
; L2 锚行同步 v3 语义(bugbear 30/7)。
; 遭遇随机化批(2026-09-12 用户拍板):D0 [1,2]/D1 [2,3] hp13/D3 [2,3] hp18——数量区间骰(encounter 流,B1 掷骰);
; D2/Boss 固定(新敌种首遇教学位不加骰——C3 GDD Core Rule 8)。期望总量 ≈304(+8%),F6 带内。
;
; 护甲类型批(2026-09-12 用户拍板:攻击类型 × 护甲类型克制;2026-09-15 命名统一批——原位置键/位置名作废):
; armor_type ∈ {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲(缺省 = light 向后兼容;原 {none, a, b});
; 克制矩阵本体在 settlement.tres.armor_effectiveness。
; 映射(教学梯度:轻甲→魔法护甲→重甲):goblin=light · bugbear=ward · ogre_chief=heavy(L2 bugbear 同 ward)——候选方案,
; 数值与映射均 provisional 待用户调参。
; 敌方攻击区间批(2026-09-14 用户拍板②第二步——"上限掺运气"敌方侧:atk 上阶/下阶,中点恒等行值;
; roll 时机 = 遭遇骰同流〔encounter〕spawn 期整战持久,每拍抖动否决〔数字可读性锚〕;B2 零随机纪律保持
; ——收 override 即确定值。宽度 ±3 provisional 归 C3 测量迭代;验收 = 完美策略档 0 加成关卡完成 ≤40%。
; 护甲等级掷点批(2026-09-16 用户拍板):armor_class 字段入列(缺省 0 向后兼容)——防守方掷点判定阈值基数,
; 与 armor_type(克制属性轴)两概念严格分离、不做映射。首关六行暂定 0(正式行值归怪物/敌人系统设定批——
; 登记待办:第一关怪物 AC 相对较低,值待怪物设定投值;投值所有权归 难度曲线与Boss)。
; 敌人技能池批(2026-09-16 用户拍板——设计权威 docs/proposal-skill-pool.md v1.0):
; pattern 行为环数组 + heavy_multiplier 整体退役,改池模板 pool = [{skill_id, weight}](技能行本体在 skills.tres;
; 实例池快照与冷却状态在 单位容器 spawn)。建议池(全部 provisional,投值归 难度曲线与Boss;EV 锚 = proposal §2.5):
; 地精 普攻 9/突刺 1(名义 EV 1.05;**整数基线 EV 1.10**——atk1 突刺落地 2:〔9×1+1×2〕÷10;2026-10-05 技能池执行批 D4 整数口径重登记);
; 熊地精 普攻 6/蓄力 2/重击 2(EV 1.00 中性);食人魔酋长 普攻 6/重击 2/硬化 2(EV 1.00 中性;
; 硬化 = Boss 池独占增益技——增益 ≤1 纪律下旧蓄力拍退役不进 Boss 池)。;
; 酋长 EV 重锚(2026-10-05 技能池执行批 T5——震地怒击半血解锁入池:前半场池 6/2/2/3,
; 后半场 6/2/2/2+震地怒击 2=每拍 4.33;含闸长期速率抽样实测 1.1948,smoke EV 抽样带重锚 [1.10, 1.30];
; 卓尔遏毒/石君王岩崩同批入池——读数判读归 T7 节奏重测〔后置挂 07,跑批待用户批准〕)
; 含闸长期速率(冷却/保底)EV 锚按实施批 EV 抽样复核登记(proposal §2.5)。
; 地精 EV 重锚(2026-10-28——触发条件批孤注守势入池(w1,血量 ≤35% 情境入池):池 9/1/1;
; EV 抽样模拟器取无情境口径(trigger 行不过滤、每拍可摇)实测 0.9148,smoke EV 抽样带重锚 [0.82, 1.02];
; 旧带 [1.00, 1.20] 系触发器前唯一特殊技=突刺的池结构下登记,随池构成换代作废)。
;
; 分支节点图批(2026-09-17——L2 幽暗地域内容批,行值全部 provisional,投值归 难度曲线与Boss):
; 每战斗/Boss 节点 (2, node_depth) 精确行(跨表校验强制,违例加载致命——ConfigDB ④);锚行 (2,0) 保留。
; L2 熊地精 = 熊地精 bugbear 复用(幽暗地域部族设定——第二关三行):
; 池差异化改造 = 蓄力拍退役换突刺(复用共享技能行 lunge——共享技能表验证面),display_name 沿用
; 熊地精系译名(熊地精/熊地精猎群/熊地精重击手——行值与池与 L1 错开)。节点 5 重击手轮廓:普攻 5/突刺 2/重击 2(名义 EV 1.333;atk1 整数口径 1.333、atk3 每拍 ≈4.11——2026-10-05 整数口径重登记;权重期望
——冷却闸实际拉低,抽样复核归实施批口径)。
; 巨蜘蛛 giant_spider(新敌种——关间台阶纪律的"新敌种"半边):群行倾向(数量区间 [2,3],首遇位节点 4
; 固定 2 不加骰——新敌种首遇教学位纪律同 L1 D2);池 = 普攻 6/撕咬 2/蓄力 1(增益 ≤1 纪律——charge 复用
; empower;EV 名义 1.00——批注旧值 1.11 = **atk1 整数口径**〔6+2×2〕÷9=1.111,非混用(2026-10-05 T2 消歧登记);护甲 light(与 L2 熊地精 ward 错开克制面;L1 三甲 goblin=light/
; bugbear=ward/ogre_chief=heavy 全覆盖,蜘蛛取 light 与同关敌种错开)。
; 蛛化卓尔精灵 drider(Boss——用户 2026-09-17 拍板定名,蜘蛛主题呼应):
; 专属池 = 普攻 6/重击 2/毒爆 2(独占技 venom_burst 权重轮廓;EV ≈1.67 权重期望,冷却闸拉低后待抽样),
; armor heavy,血量 175 = r_boss 口径 provisional 锚(定位本关最大单场消耗)。
; L2 敌种首遇位绿档(节点 0 熊地精/节点 4 巨蜘蛛):任何掷骰结果稳过——完美策略档扫描验证,读数另批登记。
;
; 数值压缩批(2026-09-17 用户拍板暂定——整体绝对值轴 ÷3,四舍五入):
; 全部 12 行 hp/atk/atk_min/atk_max ÷3(24/7→8/2 · 13/5→4/2 · 40/9→13/3 · 18/6→6/2 · Boss 150/9→50/3 ·
; 30/7→10/2 · 15/5→5/2 · 14/5→5/2 · 38/8→13/3 · 12/4→4/1 · 13/5→4/2 · drider 175/8→58/3)。
; armor_type / armor_class / unit_count 区间 / 技能池权重不动(相对轴与概率轴不压缩)。
; 粒度告警登记:atk 1~3 域下掷点折扣 25% 档大量触 damage_floor=1,地板伤害相对权重上升——
; 属压缩的已知效应,难度复测(monte carlo 压缩前后对比)随批产出。行值 provisional 归 难度曲线与Boss。
;
; 护甲类型重配批(2026-09-18 用户拍板):关卡 1 全部轻甲(食人魔酋长 heavy→light、熊地精两行 ward→light);
; 关卡 2 除 Boss(蛛化卓尔精灵 heavy→ward 魔法甲)外全部轻甲(熊地精四行 ward→light,巨蜘蛛既有 light 不动)。
; hero 侧两轴(armor_type/attack_type)同批入列(hero.tres 批注),敌方技能攻击类型轴仍未开(全部 normal 档)。
;
; 甲效矩阵加法核换装批(2026-09-18 用户拍板——settlement.tres armor_effectiveness 量纲 percent→整数点数,
; 三档 克制 +1 / 中性 0 / 被克 −1,五档百分比词表随批退役)。
;
; 新效应词汇批(2026-09-19 用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0):卓尔池
; 6/2/2 → 6/2/2/2(+蛛网束缚 web_snare 2——妨害技,对主角施 off_guard;每池妨害 ≤1 纪律入跨表校验)。
; 主分量权重期望 1.67 → 1.25(零伤害拍占 2/12),加毒爆必中附加分量(max(1,floor(atk×0.75)),atk 区间
; 2–4 → 1–3 点)后净期望待抽样复核归 难度曲线与Boss;实装面 = 仅卓尔,其余敌种不动。
; 现行读数效果:L1/L2 小怪全 light → 穿刺类(开格充能/穿刺弹)被克 −1(穿刺弹 2→1、充能 3 点→2)、普通/魔法中性;
; L2 仅 Boss 战非中性(魔法甲)——基础攻击与穿刺弹克制 +1、魔法矢被克 −1(原威力 + delta 自行核算后落笔)。
; 原重配批注「魔法矢克 Boss ×150、穿刺弹被克 ×50」与当批矩阵正反互倒(系笔误),随本批订正为上行读数。
;
; 第三关内容批(2026-09-20):新敌种 duergar 灰矮人(单体重甲中坚——关间台阶 ≈L2 单体 ×1.5,落 [1.3,1.6] 域)
; 与 hook_horror 钩爪恐魔(群行捕食者,复用 bite 共享技能行——lunge 已有 L2 熊地精复用先例);两敌种首遇位
; (节点 0 / 节点 1)固定数量不加骰——新敌种首遇教学位纪律同 L1 D2 / L2 节点 4;克制面重轻错开
; (重甲灰矮人 = 重甲首次登台,轻甲钩爪恐魔保穿刺/普通分量有用武之地),Boss duergar_king 灰矮人石君王
; 重甲收束「轻甲→魔法甲→重甲」教学梯度;Boss 血量 75 = Σ普通战生命期望 91 × r_boss≈0.82
; (provisional 锚,域 [0.5,1.1]);Boss 专属池独占技 earth_pulse(第二妨害词汇 sapped 首发);
; armor_class 恒 0(正式投值仍归「敌方护甲等级正式行值」在案条目全表同批)。行值全 provisional
; 归 难度曲线与Boss。
;
; 商店房批连带(2026-09-21——level.tres 商店房批 Boss 重编号 8→9):新增 (2,9)/(3,9) 精确行
; (跨表校验强制——ConfigDB ④ level_node_dangling),行值照抄原 Boss (L,8) 行(drider / duergar_king);
; 商店房 (L,8) 不开战斗、无行集。行值 provisional 归 难度曲线与Boss。
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 行照抄到 (2,8)/(3,8)——节点 8 现为
; 商店房、无战斗行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.1,worktree
; adventure-mc-playtest,provisional 未提交):B1 敌人 HP 阶梯整体重锚(完成度主旋钮——零加成基线
; naive 完成度普遍过低,见 qa-data/分析报告_零加成全流程.md)。只改 hp 字段;atk/atk_min/atk_max/
; 池/armor_type/armor_class/unit_count 全部不动(atk 系 §6 承伤迭代旋钮,改时须三值同步保中点恒等)。
; 改动清单:L1 d2 熊地精 13→50 · d3 双雄 6→20 · d4 酋长 50→72(d0/d1 弱档不动);
; L2 d0 10→45 · d1 5→18 · d4 巨蜘蛛 5→20 · d5 13→68 · d6 巢群 4→18 · d7 巢群 4→18 · d9 卓尔 58→85;
; L3 d0 灰矮人 15→50 · d1 钩爪 5→25 · d4 钩爪 5→20 · d5 16→76 · d6 巢群 6→20 · d7 小队 8→20 ·
; d9 石君王 75→105。以 MC 复测读数为准(runbook 计划书 §5;迭代记录 qa-data/iteration_log.md)。
;
; Package B 迭代·第 3 轮(2026-09-22——计划书 §6.2 承伤带守卫,用户拍板):8 行 atk-3 精英/boss
; 三值同步 3(2/4)→2(1/3)。第 2 轮 max_hp/heal 组合拳后单场承伤% 仍 49-62%(战斗变长吃掉血池收益,
; heal 加强主导经济修复)——§6.2「AP 已到顶 → 长战斗档 atk −1」条件成立。行清单:L1 d2 熊地精 /
; d4 酋长 · L2 d5 重击手 / d9 卓尔 · L3 d0 灰矮人 / d5 重装手 / d7 小队 / d9 石君王。
; atk-1/2 行(弱怪/群怪/L1 d3 双雄)不动。预期单场承伤 ~35%(20% 带在 Package B 框架内结构性
; 不可达,带位全达标归 Package A)。provisional 未提交。
;
; Package B 迭代·第 4 轮(2026-09-22——用户拍板 A):L1 d3 熊地精双雄 hp 20→16/单位
; (总量 [32,48])。第 3 轮后唯一未回调的 L1 高压行——双单位每轮双判定,单场承伤 51%、
; 胜率 70%,拖住 L1 通过率(40.7% vs 靶 70-78%)。其余行不动。provisional 未提交。
;
; Package B 迭代·第 5 轮(2026-09-22——用户拍板「继续」,修理刀三处,胜率带优先、完成度让路):
; ① 第一关·节点3·熊地精双雄 hp 16→14/单位(冲 ≥90 胜率靶;该行胜率/完成度两靶互斥已登记);
; ② 第二关·节点9·蛛化卓尔精灵 85→80(L2 boss 胜率 59% 回 60-75 带);
; ③ 第三关·节点9·灰矮人石君王 105→92(L3 boss 胜率 38% 向 50-70 带靠;该行完成度靶未设带,让路合法)。
; 否决项登记:食人魔酋长 +10% 方案经测算否决——boss 完成度 >130 需 HP +~50-75%(98→104 买不回靶),
; 会把刚入带的 66% 胜率打回带外,纯亏。provisional 未提交。
;
; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md,
; 用户三拍板 1A/2A/3A,第 7 轮):第二/三关阶段拔高 14 行(L1 一行不动)。血轴=每关上一档;
; 伤害轴=atk 2→3 三值同步 3(2/4)【L2 d5 重击手/d9 卓尔;L3 d0 灰矮人/d1 钩爪/d4 钩爪/d5 重装手/
; d6 巢群/d7 小队/d9 石君王】。清单:L2 d0 45→50 · d1 18→22 · d4 20→24 · d5 68→88+atk3 · d6 18→22 ·
; d7 18→22 · d9 80→112+atk3;L3 d0 50→62+atk3 · d1 25→30+atk3 · d4 20→26+atk3 · d5 76→98+atk3 ·
; d6 20→26+atk3 · d7 20→26+atk3 · d9 92→140+atk3。
; 目标对齿(熟练玩家):S0 零成长 L2 达 boss ~20%/通 ~0%、L3 ~0;S1(≈5 次 L1 关卡完成锻造后,hero 快照
; 攻4/血26/甲11)L2 达 ~70%/通 ~50%;S2(攻6/血32/甲12)L3 通 ~55%。验证 = hero.tres 阶段快照 MC
; (设计文档 §6,快照后恢复基线)。验证出带微调旋钮序:L2 d1/d6/d7 血 22→20 → d4 24→22 → d9 112→108。
; provisional 未提交。
;
; 实体名修正批(2026-09-24——用户裁决,DND 阶梯对齐):(1,3) 熊地精双雄 → 大地精
; (enemy_id bugbear→hobgoblin,仅本行;L2 bugbear 三行熊地精系不动)。依据:①DND 阶梯
; 地精(goblin)→大地精(hobgoblin)→熊地精(bugbear) 与实测每只血量 4-8→14→50 严格同构——
; 单只熊地精 50 血在前、三只 14 血在后,同族每只血量倒挂的观感由阶名归位化解;
; ②「双雄」名硬编码数量、数量却是 [2,3] 变量骰——摇出 3 只即成「三个双雄」自相矛盾
; (用户截图实证),集体名(猎群/巢群/小队/大地精)为正解。hp/atk/池/数量区间零改动。
; M-3 迭代 5 判读锁值批(2026-09-28):(2,9) 卓尔 hp 130→100(杠杆扫描 C3 组合——Boss 杠杆对普通档 +4pp;
; r_boss 锚漂移加重 100/389≈0.26 低于域 [0.5,1.1] 下限,收锚裁决挂迭代 5 出口人闸;读数见
; design/qa/m3-iter5-lever-scan-2026-09-28.md)。
; M-3 迭代 5 T4 AC 投值批·终版 Boss-only(2026-09-28:v1 硬梯度普通 L2 14.3% 越带/v2 软梯度 15.7% 仍越带——杂鱼税对普通画像〔普通分量为主力输出〕系统性超压;终版=杂鱼全 0〔带 0~5 下缘,掷点戏剧性集中 Boss 战〕+Boss 台阶 8/9/12 严格递增;三版演化链读数见 design/qa/m3-iter5-t4-ac-2026-09-28.md,随出口人闸追认)。
; M-3 迭代 5 返工批·U2 裁 v1 硬梯度重投(2026-09-29 用户推翻 Boss-only:重击手 8/重装手 9〔逐行手设值,非精英编码位——解读 A 追认,精英 P2 承载〕+Boss 台阶 8/10/12;杂鱼行按 T4 报告 §一 软梯度恢复〔v1 中间态未入 git,v1↔v2 唯一差异=重击手/重装手,代拟面记台账 247〕;卓尔 hp 100→130 随 C3 回退复原;组合读数 C1+v1 无先测,全量确认归返工批——裁断全录 design/qa/m3-iter5-u2-gate-2026-09-29.md)。
;
; 数值修复批·调参二(2026-10-01 新尺 MC 驱动):L1 系八行 atk 2→1(行 15/35/54/77/99/121/144/166;
; 行 234 巨蜘蛛巢群主体=L2 域维持)——新尺读数:普通档 v3.4 死因=L1 四连战无治疗夹缝(雷伤 6/8/8
; + 杂兵垫刀双压,L1 条件关卡完成率 1-2%,0/100 出第一关);完美档战役 50%(L1 93%)。刀位语义=
; 「危险来自雷,不来自杂兵」:杂兵垫刀减半,雷直伤 6/8/8 维持痛感;L2/L3(atk 3 系)本刀不动。
; 全旧带读数随新尺作废重测归本批。
; 调参二补充:上述八行 atk 区间 1-3→0-2(中点恒等期望当量锚不变式——atk=1 需区间中点同步 1;0=掷点空刀,保随机手感)。
;
; 混编遭遇首批投放批(2026-10-07 迭代 18 卡⑤——需求卡片 v1.2 裁点全裁 P1-P4 甲;机制前置=敌人表行集多行批
; 已提交 9cc4b8b):首个混编遭遇行落表 = (2,5) 双行行集〔R6 行序契约:同坐标两行文件内相邻、
; 行序=声明序=生成序——resolver 返回序/生成序/断言面全依赖文件行序〕:
; 行 1 = 既有 熊地精重击手 (2,5,bugbear)(行值零变更);
; 行 2 = 新增 (2,5,giant_spider) 巨蜘蛛巢群——行值整行复制 (2,6) 巢群行(P4 甲零数值新增:unit_count 2/
; unit_count_max 3/hp 26/atk 1/atk [1,2]/池 普攻6·撕咬2·蓄力1/light/armor_class 2),
; 行序=重击手在前(默认目标=前排坦克 ac 8,蜘蛛后排输出——强弱搭配形态)。
; 设计意图(P1 甲单点试水):「巨蜘蛛+熊地精」同场混编——生产内容实打卡①「每行一骰数量骰」交付面
; (重击手定值不骰、蜘蛛掷 [2,3],同场两种数量语义并存);静态最坏骰和 = 1+3 = 4 ≤ ENEMY_ROWSET_INSTANCE_MAX
; (spawn 期守卫内)。
; 读数预期:(2,5) 场敌人 3-4 只(3=重击手 1+蜘蛛 2,4=蜘蛛掷 3);L2 难度带位移读数随 Alpha 收官 M4 数值批
; 统一执行(07 在册义务——MC 取证跑批届时另报批,本批零跑)。禁区排查:巨蜘蛛首遇位 (2,4)「固定 2 不加骰」
; 零触碰、Boss d9 零触碰、跨关混编禁令过(两敌种首遇关均 ≤L2)、既有 19 行现值零变更。
; 敌人表内容修批 N1(2026-10-13 用户令「按推荐方式」,迭代 24 卡①):(1,2) 熊地精降档 hp 50>>22、armor_class 3>>2——
; 2026-09-24 阶名化解方案((1,3) 改名大地精以阶名归位化解同族血量倒挂观感)实机验证未达预期,按体验反馈重裁:
; 第一关总量曲线 12/10/50/35/72 的 5 倍墙消除(22 后曲线 12/10/22/35/72 相邻比最大 2.2);敌种 bugbear/池/数量/atk 区间零改动。
; 曲线标定其余行归 M4 数值批。
; N3 敌方攻击类型轴开列(2026-10-13 迭代 24 卡①,承接锻造工坊实装批迁移面⑥):20 行全填 normal 挂敌种行——
; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0,
; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。
;
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss}——battle=战斗节点配池,boss=终点行集;
; depth_band=节点深度所在的带键,请求落带取首个 ≥ 深度的键(偏难一侧);越顶带由 resolver clamp、跨表校验判为调参事故。
; hobgoblin 行为缺口行:无立绘、不在敌种表——留任在役,补齐三件套(立绘+语义模板+敌种表行)归敌种内容批。
+221
View File
@@ -4,6 +4,17 @@
[resource]
script = ExtResource("1")
; 迭代 20 专长实装批 T3——专长重设计 27+2 件换代(奇物.md 基础件表 27 件+迁入登记 2 件=29 行;
; 件数口径=卡文 §10.4 推荐案:29 行=基础 27+迁入 2,V2/V3 断言按落表实数)。旧 27 件随本批整体
; 退役(素材库表二);订阅行 on/when/do 词表=效果引擎封闭集;rarity 四级直映射令牌档;cap=已拿
; 上限(案②解耦——拿了才计刷出不计);数值全 provisional 归调参批;desc 禁「雷/旗」字面
; (契约 6 命名纪律:旗〔旧称〕=封印、危险格=陷阱/巢穴风味层)。dispatch 预评估口径注记:「每第 N 次/积
; 满触发」系 when 于命中前读计数器现值——行 when 按 N-1 落值(双管镐 mod_eq ref 1/地质学家 eq 3/
; 跳格 eq 2/封印匠 mod_eq ref 2/保险丝 eq 1——effect_words_t1_test mod_eq 断言钉住该口径)。
; 迭代 31 卡② 凭证件裁削批 T5——完美保险自资源类迁入专长表(第 30 行):rarity rare/cap 省略
; (承 twin_pick/seal_burst 惯例)/domain=battle 显式写出(现役 29 行全为 adventure 或不写,本行
; 系战斗域首例——豁免计数每战清零)。限次落法=单订阅行:when counter(insurance_used) lt 1 承载
; 「本场首次」,do 自带 counter_id 记账由授予段消费点落账(dispatch 不代执行计数器)。
rows = [
{
"feat_id": "twin_pick",
@@ -858,5 +869,215 @@ rows = [
}
}
]
},
{
"feat_id": "vein_reserve",
"display_name": "矿脉储能",
"rarity": "excellent",
"cap": 1,
"desc": "挖格储能:每次受理的付费挖格 +1 储备点(RAP)",
"triggers": [
{
"on": "dig_paid",
"do": {
"kind": "grant_reserve_ap",
"value": 1
}
}
]
},
{
"feat_id": "easy_swap",
"display_name": "省力周转",
"rarity": "excellent",
"cap": 1,
"desc": "刷新半价:弃图刷新成本为基线 50%",
"triggers": [
{
"on": "refresh_request",
"do": {
"kind": "apply_discount",
"mode": "refresh",
"cost_mult": 50
}
}
]
},
{
"feat_id": "wall_blast",
"display_name": "崩壁冲击",
"rarity": "excellent",
"cap": 1,
"desc": "刷后群伤:成功刷新后全体存活敌各受 2 点",
"triggers": [
{
"on": "board_refreshed",
"do": {
"kind": "open_ticket",
"channel": "blast",
"value": 2
}
}
]
},
{
"feat_id": "mine_profit",
"display_name": "矿道红利",
"rarity": "rare",
"cap": 1,
"desc": "刷后GP:每次成功刷新固定 +1 GP",
"triggers": [
{
"on": "board_refreshed",
"do": {
"kind": "grant_gp",
"value": 1
}
}
]
},
{
"feat_id": "stone_mitigation",
"display_name": "石层缓冲",
"rarity": "common",
"cap": 1,
"desc": "当轮减伤:本轮成功刷新后,每次受伤固定 −2(多刷不叠加)",
"triggers": [
{
"on": "board_refreshed",
"do": {
"kind": "counter_add",
"counter_id": "round_swap_guard",
"value": 1
}
},
{
"on": "damage_taken",
"when": [
{
"fact": "counter",
"id": "round_swap_guard",
"op": "gte",
"value": 1
}
],
"do": {
"kind": "damage_reduce",
"value": 2
}
}
]
},
{
"feat_id": "charge_rotation",
"display_name": "蓄力周转",
"rarity": "excellent",
"cap": 1,
"desc": "刷后蓄力:每次刷新 +1 层(上限 3),下一次攻击消费全部层数",
"triggers": [
{
"on": "board_refreshed",
"when": [
{
"fact": "counter",
"id": "swap_charge_layers",
"op": "lt",
"value": 3
}
],
"do": {
"kind": "counter_add",
"counter_id": "swap_charge_layers",
"value": 1
}
},
{
"on": "attack_commit",
"when": [
{
"fact": "counter",
"id": "swap_charge_layers",
"op": "gte",
"value": 1
}
],
"do": {
"kind": "attack_bonus",
"value_counter": "swap_charge_layers"
}
},
{
"on": "attack_commit",
"when": [
{
"fact": "counter",
"id": "swap_charge_layers",
"op": "gte",
"value": 1
}
],
"do": {
"kind": "counter_add",
"counter_id": "swap_charge_layers",
"set": 0
}
}
]
},
{
"feat_id": "tunnel_rest",
"display_name": "坑道小憩",
"rarity": "common",
"cap": 1,
"desc": "当轮恢复:每次刷新累计 1 点(上限 3),本轮轮末合计回复",
"triggers": [
{
"on": "board_refreshed",
"when": [
{
"fact": "counter",
"id": "round_swap_rest",
"op": "lt",
"value": 3
}
],
"do": {
"kind": "counter_add",
"counter_id": "round_swap_rest",
"value": 1
}
},
{
"on": "round_end",
"when": [
{
"fact": "counter",
"id": "round_swap_rest",
"op": "gte",
"value": 1
}
],
"do": {
"kind": "heal",
"value_counter": "round_swap_rest"
}
},
{
"on": "round_end",
"when": [
{
"fact": "counter",
"id": "round_swap_rest",
"op": "gte",
"value": 1
}
],
"do": {
"kind": "counter_add",
"counter_id": "round_swap_rest",
"set": 0
}
}
]
}
]
+11
View File
@@ -85,3 +85,14 @@ routes = [{
"prereq_route": "light",
"unlock_price": 200
}]
; 锻造工坊实装批 T1(2026-10-12——迭代 23;设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6,
; 需求卡片=锻造工坊实装批(迭代 23,卡片已按「吸收即死」退役,git 历史可考)T1 锚表):
; 六路线 provisional 投值——cap 5/每级增量统一 +1(v1.2 用户裁:provisional 线性起步,4a 前缓后陡为
; 终值形状方向归 M4)/甲型基础 AC 10-12-9(现役结构值 settlement.tres 矩阵同行)/基础价 20/公比 150%
; (历史考古参数 meta.tres:82-84 退役批注)/各级价格 20/30/45/67/101/一次性解锁价 200(中段三级 213 九折)。
; 初始持有=长剑+轻甲(语义继承自已退役属性锻造行——迭代 21 T5 行族删除后的新建行族,非改造)。
; 并联双通道(4b-1 裁):长枪/法杖←长剑练满、重甲/魔法护甲←轻甲练满,或一次性解锁价直购,互不影响。
; 默认两轴(契约 2 所有权移交):hero.tres 静态表两轴随本批删除,默认值归本表常驻持有——normal/light
; = 裸体态类型(Q1:未解锁/未锻造=0 加值)。全部数值 provisional 终值归 M4 数值批。
; 表运行期只读(铁律):永久强化只写存档 forge_routes 域,本表零写面。
+28
View File
@@ -8,3 +8,31 @@ max_hp = 26
atk = 2
armor_class = 10
display_name = "探险者"
; 〔2026-10-12 迭代 23 锻造工坊实装批 T2——两根类型轴删除(契约 2 所有权移交):armor_type/
; attack_type 两字段随本批从静态表删除,默认值归锻造工坊配置常驻持有(forge.tres
; default_attack_type=normal/default_armor_type=light),选中态写存档域 forge_routes、冒险开始
; 经参数面注入;单位快照读取面改读注入值(缺省回退锻造配置默认值——行为连续)。原 2026-09-18
; 营地扩展前置批「两根可配置轴」登记随字段删除退役,历史记载归 git 考古。〕
; 数值压缩批(2026-09-17 用户拍板暂定——整体绝对值轴 ÷3):
; max_hp 50→17(16.67 四舍五入)、atk 6→2。armor_class 10 不动(掷点阈值轴,相对量纲不压缩——
; 用户同日裁决:本体自带 AC 不算减伤统计)。恢复奖励削弱随批在 reward.tres(概率 1/4 + 量再减半)。
; 全部行值 provisional 归 难度曲线与Boss;压缩前基线读数(N=30 normal):基线档通关率 3%。
; 护甲等级掷点批(2026-09-16 用户拍板):armor_class = 10(暂定基准,归 难度曲线与Boss;
; EV 参考数按判定式复算 = 期望减免 21.25%);ac_up 增益 +1 AC/层(×2 层 = AC+2)在其上加算。
;
; 〔营地扩展前置批(2026-09-18 用户指令)两根可配置轴 armor_type/attack_type 已于 2026-10-12
; 迭代 23 锻造工坊实装批 T2 删除——所有权移交锻造工坊配置(见顶部注记);甲型基础 AC 随甲路线
; 行承载(轻 10/重 12/魔法护甲 9),本表 armor_class=10 保留为甲路线缺失时的兜底基准。〕
;
; Package B 迭代·第 2 轮(2026-09-22——计划书 §6.2,用户拍板一B):max_hp 17→20。第 1 轮读数
; 触发 §6.2(L1 精英承伤 43-55% vs 靶 ≤20%、L1 净消耗 17.7/17=104% 破产、normal L1 通过率
; 8.3% vs 靶 70-78%)的生存侧修复。单变量归因原则:同轮第二变量(heal_minor 加强)在 reward.tres。
; 血线 17/27/37→20/30/40(关间大奖励 +10 不变)。provisional 未提交。
; 数值修复批(2026-10-01——模拟档位重构后新尺首调):max_hp 20→26(调参二再 +2:普通档 L1 7% 差 2-4 血量级,+8% 池把 b3/b4 压线死改写险胜;完美档 62% 上界余量健康)。依据=新尺 MC 读数:普通档 v3.4
; (完美+失误注入,踩雷 ~2.5/战)L1 四连战无治疗夹缝全灭(L1 条件通关 2%,0/100 出第一关);
; 完美档战役 36-41%(L2 53% 弱环)。+20% 血池抬夹缝生存;配套直伤雷当量同步上调(reward.tres
; 4/5/5→6/8/8,雷痛回归用户口径)双向对冲。全旧带读数随新尺作废重测归本批。
+4
View File
@@ -10,3 +10,7 @@ rows = [
{"level_index": 2, "display_name": "第二层 · 幽暗地域", "rest_heal_pct": 30},
{"level_index": 3, "display_name": "第三层 · 灰矮人矿城", "rest_heal_pct": 30}
]
; 节点图源已换代为参数化生成器(关卡节点图生成 v1.1)——本表逐关行只承载关卡序号/显示名/恢复比例,
; 不再携带节点数据;图 = f(参数档, 种子),参数档见 level_gen_params.tres 三行(消费侧按 level_index 解析后交 generate)。
; 旧 15 张模板池(3 关 × 5)已清出,历史查 git 提交史与 tools/t5_build_templates.py、t17_fanout2_rebuild.py 留档。
+3
View File
@@ -44,3 +44,6 @@ rows = [{
"fork_ways_min": 2,
"fork_ways_max": 3
}]
; 节点图生成器核心批 卡① §8.1 参数档 provisional 取值表逐行填列(定值批落终值,数值全 provisional)。
; 层深/出口/岔口去路数为已锁域;商店实数由摆放约束钉死=出口数(shop_ratio 为名义值,配比填余次序见卡① §8.1 注)。
+40
View File
@@ -36,3 +36,43 @@ attune_refresh_upgrade = [{
"refresh_charges": 2,
"keystone_cost": 60
}]
; 元进度实现批(2026-09-21,设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2):
; 数值全 provisional 归元进度设计批配读数(骨架先行——元进度实现批 2026-09-21,先例 = GP+商店房批)。
; 〔forge_products 属性锻造行族已退役——迭代 21 T5 攻防所有权切分:生命唯一入口=树脊柱淬炼、
; 攻/AC 唯一入口=锻造工坊路线(契约 9 红线),meta_forge_row 访问器与 buy_forge 购买面随批删除;
; 历史投值记载(2026-09-22 对齿批 base 20/ratio 150、2026-09-23 标定批 6/9/1)系 conversion 行
; 与 ForgeGDD 考古用,行族本体不再在表。〕
; 〔workshop_items 道具工坊行族已退役——迭代 22 T2 秘宝实装批:解锁换代混合制(钥石购买价
; unlock_keystone_cost + 条件谓词 unlock_requires 全挂 treasure_items.tres 行内单一事实源),
; 旧「总通关次数达阈值」六行(阈值 1/1/2/2/3/3)与渠道面随批删除;历史记载归 git 考古。〕
; boss 两档 L1 5/1 · L2 8/2 · L3 12/3
; (首杀全量、重复杀固定小额恒定不递减);conversion 全 0 = 未标定占位(标定归设计批,护甲行强条目:验收读数必须按
; 解锁后参数重测)。
;
; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md,
; game-balancer 工作流,用户三拍板 1A/2A/3A):forge base_price 10→20(三行同,ratio 150% 不变,
; 效果档 血+3/攻+1/甲+1 不变)——S1 一档(攻+2/血+6/甲+1 ≈ 120 GP)对齐「≈5 次 L1 关卡完成开门」节奏
; (实测 L1 通关局收入 ≈29-35 GP)。换算率表维持全 0(墙/钥匙闭环不依赖该轴,留扩展)。
; 配套怪表改动见 enemy.tres 同日批注。provisional 未提交。
;
; 换算率标定批(2026-09-23——用户指令「现在标定」;读数报告 production/qa/conversion-calibration/
; calibration.raw.md,判定端 tools/balance_verifier.py ④ 段可复判):conversion 三行全 0 → 6/9/1 投值。
; 方法 = OFAT 冒险注入档(每属性 +1/+2 级 × 双画像 × na=100,与白板同种子流配对)逐关条件
; 通关率差实测每级带平移 生命 +6.0±0.7 / 攻击 +8.8±0.8 / 护甲 +1.2±0.4 点(配对 bootstrap SE,
; 逆方差加权合并)→ 取整投 6/9/1。测量面 = 冒险条件口径(同日单关直入口口径退役后重测——
; 首轮直入口面读数作废)+ 当日 L2 收紧/关间大奖励 10→5 后的现行数值包。
; 护甲行强条目重测义务随批履行:重测暴露并修复护甲断链(begin_battle hero_armor_class 参数此前
; 零消费——_defender_ac 主角分支本次接线,缺参/-1 = 表值兜底);修复后甲轴读数 +1.2±0.4 为
; 解锁后参数实测,非基线外推。战役轨迹三花费序(生命/攻击/护甲优先)随批采集验证。
;
; 迭代 10 奖励重构批(2026-10-01——boss_keystone 重锚,P-B 乙拍板):首杀 3 三关统一;
; 复杀按关 2/2/3 递进(**用户 2026-10-01 裁「a 用备选」**——原代拍板「复杀统一 2」被推翻;
; L3 首杀=复杀=3 系 first_kill ≥ repeat_kill 结构约束下的合法端点)。
; 钥石渠道自本批起 =
; Boss/精英击杀逐杀掉落(大奖励 keystone_per_level 渠道退役;精英掉 1 预留行随怪物批实装落)。
; 产出结构变化 → 元进度标定义务重开(06-待办清单 2026-10-01 登记条)。provisional 归曲线调参批。
;
; 迭代 11 第①批 同调实施批 T4(2026-10-03):attune_refresh_upgrade 行族 = Y 刷新升级行(需求卡片 P1 甲——
; 每冒险开局给 Y 次、冒险周期池、用完即无;等级 L → Y=L 次刷新,钥石直购)。数值全 provisional 归
; 难度曲线与Boss(需求卡片 §三 7)。
+13
View File
@@ -155,3 +155,16 @@ rows = [{
"economy_spend": 1
}
}]
; 迭代 21 天赋树实装批 T1 换代(2026-10-12,设计权威 design/gdd/systems/跨局循环/天赋树.md v0.8):
; 12 节点 = 脊柱 2(生命淬炼/命中淬炼——一线一节点 cap4,2026-10-11 用户裁,原 4 层×4 线 16 行收编;
; 攻/护淬炼线随攻防所有权切分砍除——攻唯一入口=锻造工坊武器路线、AC 唯一入口=甲路线〔契约 9 红线〕)
; + 锻造脉 2(forge_workshop 钥匙 free 无前置 + easter_ley 彩蛋——effects 只持生命/命中两键,
; :402/:405 旧攻/护分量已摘除)+ 钥石脉 2(秘宝匣 ks_workshop〔display 旧名「道具工坊」退役〕/
; 刷新升级 ks_refresh)+ 同调脉 6(同调位 1/2/3——socket1 改根节点无前置 + Fork 3 单行双态节点,
; 原 8 行 A/B 双行结构与 fg_mastery 克制专精组退役)。
; 行门槛=一线一节点主语:脊柱节点第 N 级需全树总等级 ≥4×(N−1)(SPINE_ROW_GATE=4 公式平移,
; row 字段退役);requires=点亮前置(R1 裁撤案执行,schema 无等级阈值形态)。
; 🔴 层级纪律:局外树只放全局规则件——零 archetype 键(schema 校验强制);规则改写型特性只走局内随机渠道。
; 数值全 provisional 归 M4 数值批(契约 7——价格沿旧量纲锚:脊柱 10=旧 r1 价、socket 20/40/60、
; Fork 25、刷新 30 沿旧;生命 +5/级、命中 +1/级沿旧量纲);A/B 两侧差 <~3pp 验收=V19 MC。
+59
View File
@@ -201,3 +201,62 @@ rows = [{
"pool_tag": "cell",
"tier": 1
}]
; 格面资源批(2026-09-15 用户拍板——registry reward_table 条目为现行 schema 事实源):
; 十行格面池(五型 × 正负各一行)+ 清图槽一行。安全格与危险格同池同分布(雷安同池),
; 装板期一次摇定;摇号只消费正向行权重(类型频次 = attack 20 / defense 16 / blast 16 / heal 3 / weaken 12,合计 67;
; 头段"heal 15 / weaken 12,合计 79"为历史注记——数值压缩批后 heal 3、合计 67,见本文件尾部 2026-09-17 批注);
; 负向行 weight=0 不参与摇号,纯(类型, 极性)兑付字典——heal 负向 = 主角直伤 4(不经增益状态),
; blast 负向 = 警觉(敌方 AC +1,2026-09-20 伏击退役换装)。
; 可选类三行(knife_t1/knife_adv/heal_pack)与注入池 active_inject 随可选类道具整体删除(供给控制器/保底/窗口同步下线)。
; 格面资源八型批(2026-09-20 用户拍板):① blast 负向 ambush_double 退役 → enemy_alert(警觉,effect_id=alert
; 敌方 AC +1 percent 行);② 新增第 8 型 action 两行——ap_up(正向,下轮行动点 +1)/ ap_drain(负向,下轮
; 行动点 −1),排队兑现于下一轮开始(action_economy.queue_ap_delta);③ off_guard 数值 −2→−1 归 status_effects.tres。
; 区间行(伤害量值区间批 2026-09-14):heal_minor [2,6] · clear_blast [25,55]——中点锚 power 恒等期望当量,
; roll 时机 = 身份预分配批内追加取号(既有取号序零扰动);spark_aoe 定值行无区间字段。
; 减伤墙调参批(2026-09-13):defense_up 权重 24→16——攻击成为池内最高行(历史注记)。
; 护甲等级掷点批(2026-09-16 用户拍板):defense 型正向行换装——reward_id/effect_id defense_up → ac_up
; (显示名"盾牌加护"——2026-10-12 由"护甲强化"改名;AC +1/层 ×2 层);cell_type defense / 权重 16 / tier 2 零改动(类型频次合计 79 不变——历史注记,数值压缩批后现合计 67);
; 百分比减伤行无双轨退役——status_effects.tres defense_up 行同批删除。
;
; 数值压缩批(2026-09-17 用户拍板暂定):
; ① 整体绝对值轴 ÷3:spark_aoe 4→1 · heal_backlash 4→1 · clear_blast [25,55]→[8,18](中点 power 40→13,
; 区间中点恒等期望当量的锚口径不漂移)。
; ② 恢复奖励削弱(用户拍板两条):出现概率下调到当前的 1/4——权重 15→3(当前频率 15/79≈19.0%,目标
; 4.75%,整数权重最接近 = 3/67≈4.5%);恢复量在 ÷3 后再 ÷2(现值 ×1/6)——[2,6]→定值 1
; (0.33~1 取整,区间字段保留 min=max=1 占位,量级再调时恢复区间结构)。
; ③ 雷安同池连带(结构冻结,自动同侧):heal 权重下调同比例降低踩中 heal 型雷(反噬)的频率——
; 负向侧反噬量同步 ÷3(4→1)。
; 类型频次合计 79→67(attack 20 / defense 16 / blast 16 / heal 3 / weaken 12)。行值 provisional 归 难度曲线与Boss。
;
; 伤害道具批(2026-09-18 用户指令):① 七型字典——五型增补 pierce(穿刺)/ magic(魔法)两型,各正负一行:
; 正向 = 单目标克制直伤道具(穿刺弹/魔法矢 power 2,attack_type 字段载攻击类型轴,走克制矩阵、
; 必中全额不掷点——棋盘来源伤害既有口径);负向 = 主角直伤(穿刺陷阱/魔法反噬 power 2,复用反噬通道不经增益状态)。
; ② 权重再平衡(保 spark_aoe 16 不动、总 67 不变):attack_up 20→14 · ac_up 16→11 · heal_minor 3→2 · weaken 12→8;
; 新行各 8。等比基准 = 其余四行 51→35(×0.686),整取后 14/11/2/8,微差登记已知偏差。
; 类型频次合计仍 67:attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8。
;
; 九型批(2026-09-20 用户拍板——GP 进第九型,获取来源=战斗结束掉落+雷图低概率惊喜获取〔量与战斗掉落相当〕;
; GP 袋=挖开安全格入战斗内缓冲、战斗完成统一入账;破财=危险格摇到 gp 面的字典负向行〔weight 0 不入池,
; 挖危险格兑付=缓冲 -2 钱包侧 clamp ≥0〕;正向权重合计 74→76);行值 provisional 归 难度曲线与Boss。
; gp 两行 tier=1(provisional 占位);effect_id gp_gain/gp_loss = 战斗内缓冲(escrow)通道路由键
;(战斗结算 _apply_percent_row 分流,不入增益容器——ConfigDB ① status_effects 悬空豁免归 core 面)。
;
; Package B 数值优化批·首版(2026-09-22——qa-data/实现计划书_PackageB.md §4.3):clear_blast
; 区间 [8,18]→[18,35](中点 power 13→26)——清第一图回归真正伤害尖峰,撑起 boss"必清图才打得动"
; 节奏(L1 boss 清图≥1 靶 ≥80%);其余行全部不动。provisional,以 MC 复测读数为准
;(runbook 计划书 §5;迭代记录 qa-data/iteration_log.md)。
; ⚠️ 计划书笔误代拍板:原文"power 25"违反 schema 中点锚硬校验(config_fatal)——power ≡ floor((min+max)/2),
; (18+35)/2=26 故落 26(期望当量 +1 vs 计划书 25,掷点区间 18–35 与计划一致,玩法值零偏离)。
;
; Package B 迭代·第 2 轮(2026-09-22——计划书 §6.2,用户拍板一B):heal_minor 恢复量 1→2
;(区间三值同步 power/min/max = 2/2/2 过中点锚校验)+ 出现权重 2→4(正向池合计 76→78)。
; 与 hero.tres max_hp 17→20 同轮双变量(用户拍板组合拳,归因混在一起由下轮分摊)。provisional 未提交。
;
; Package B 迭代·第 4 轮(2026-09-22——计划书 §6.4,用户拍板 A):clear_blast 26→32
;(区间 [18,35]→[24,40],(24+40)/2=32 恰为整中点锚)。boss 面三缺同解(normal 胜率 62% 贴下沿 /
; 清图率 49% vs ≥80 / 完成度 99 vs >130)——清图尖峰加大让"先清一图"成为 boss 战最优路径。
; 副作用已知:perfect L1 boss 胜率 97% 越 75% 上界加剧。provisional 未提交。
; 数值修复批(2026-10-01):直伤雷三行 4/5/5→6/8/8(迭代 8 雷当量 provisional 的再抬档)——用户口径
; 「踩雷必须痛」;配套 hero max_hp 20→24 双向对冲(见 hero.tres 批注)。新尺 MC 复测归本批。
+54
View File
@@ -63,3 +63,57 @@ script = ExtResource("1")
segment_order = PackedStringArray("grant", "buff_debuff", "combat", "decay", "verdict")
core = SubResource("SettlementCoreTable_core")
damage_roll = SubResource("DamageRollTable_roll")
; 攻击类型 × 护甲克制批(2026-09-12 用户拍板结构;2026-09-15 命名统一批 + 抽象分离批,数值 provisional 待用户/C3 调参):
; 主角攻击 = 单一基础分量(2026-09-19 充能退役批·用户拍板——原三分量模型退役,克制策略归冒险前配置:
; 类型 = hero 表 attack_type,营地换型 = 改字段;局内无挖掘攒弹药的第二供给)。
; 【抽象分离纪律保留】攻击类型 {normal, pierce, magic} = 普通 / 穿刺 / 魔法 = 伤害的属性轴,
; 只决定查矩阵哪一行,定义里不含任何棋盘来源;护甲类型 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲是敌单位属性。
; 格面资源批(2026-09-15 用户拍板):段序七段 → 五段(item_window/item 两段随可选类道具删除下线);
; source_label_map 删 item 键;effect_pool_map 增 sapped→hero_offense(weaken 型负向——乏力)。
; enrage(敌方暴走 +25%,enemy_output 池)/ off_guard(我方失衡 +25%,hero_defense 池)/
; sapped(我方乏力 -25%,hero_offense 池)= attack/defense/weaken 三型负向字典行
; (极性路由兑付:挖危险格 / 标错安全格按该格 cell_type 取负向行;heal 负向 = 主角直伤不经状态、blast 负向 = 警觉(敌方 AC +1,2026-09-20 八型批换装))。
; 减伤墙调参批(2026-09-13):pool_clamp 域 [-100,+100] 结构冻结不动;减伤杠杆走 status_effects.tres(批注在该表)。
; 护甲等级掷点批(2026-09-16 用户拍板,数字即规范——战斗结算 GDD 护甲等级掷点节定稿同批):
; roll_faces=20 / roll_tier_bounds=[3,7] / roll_tier_discounts=[75,50,25] / crit_roll=20 / crit_multiplier=200 /
; damage_floor=1 / armor_roll_stream=armor_roll;attack_bonus=0 结构位(元进度/武器附魔未来投值)。
; 判定式:掷点 ≥ 防守方 AC + attack_bonus → 全额;缺口 1-3 → ×0.75、4-7 → ×0.5、8+ → ×0.25;无 miss 无零收益档;
; nat20 = 暴击 ×2 恒全额(双方同规);任何攻击落地伤害 ≥ damage_floor(全局 max(1,·))。
; 掷点只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线);穿刺/魔法充能与棋盘来源伤害必中全额。
; EV 参考数(按判定式复算):AC10 期望减免 21.25% / AC11 25% / AC12 28.75%;微调旋钮 = bounds/discounts,不动 AC 值。
; effect_pool_map 同批删 defense_up 键(减伤百分比行整体退役、无双轨)——ac_up 是 AC 阈值轴效果,不入任何池;
; off_guard(hero_defense 池)保留不动,池结构与乘区公式零改动。
; 敌人技能池批(2026-09-16 用户拍板):effect_pool_map 增 empower→enemy_output(蓄力技载体——敌方输出 ×2
; 经既有 enemy_output 乘区兑付)与 fortify→enemy_defense(硬化技载体——敌方防御池新词汇)。
; enemy_defense 池乘区消费面 = 主角攻击落地段(Σ三分量后 ×(100+clamp(enemy_defense))/100)——
; 四池对称乘区:敌打主角 = hero_defense×enemy_output,主角打敌 = hero_offense×enemy_defense
; (hero_offense 现行走主角普通分量 attack_up 直乘、切片未改——登记遗留)。
;
; 数值压缩批(2026-09-17 用户拍板暂定):
; equivalence_ref_damage 12→4(当量参考敞口,绝对值轴 ÷3 取整);
; roll_faces/折扣档/暴击/damage_floor/克制矩阵/pool_clamp 全部不动(相对轴)。clamp 域 [-100,+100] 共用。
;
; 战斗数值结构修复批(2026-09-18 盘问会话用户拍板):
; effect_pool_map 删 off_guard 键——off_guard(失衡)换装 AC 轴(unit percent→ac、+25→−2,provisional
; 归 状态层重设计批;消费走 _defender_ac 既有 ac 型读法零代码),退离 hero_defense 池(池清空、结构保留;
; 上方 2026-09-15 attack/defense/weaken 三型负向字典行的极性路由注记不变——defense 型兑付行仍是 off_guard,
; 变的只是池归属)。hero_offense 乘区接线同步落地(battle_orchestrator 主角攻击段):Σ三分量后
; ×(100+clamp(hero_offense))/100×(100+clamp(enemy_defense))/100——attack_up 普通分量直乘旁路退役,
; 增伤/乏力覆盖面 = 普通+穿刺+魔法 合计;上方"hero_offense 现行走直乘——登记遗留"注记随之销项。
;
; 甲效矩阵用户调参批(2026-09-18 用户逐格给值,五格改值、四格不动):
; normal ward 100→120 · normal heavy 100→50 · pierce light 100→80 · pierce ward 150→120 · pierce heavy 50→80;
; magic 全行与 normal light 不动。上方「克制语义 = 克制 ×150 / 中性 ×100 / 被克 ×50(对策三角)」三档措辞退役——
; 现行五档:克制 ×150 / 弱克制 ×120 / 中性 ×100 / 弱被克 ×80 / 被克 ×50,普通行不再恒中性。
; 键集 3×3 满格结构冻结不动;矩阵消费面 = 主角侧(道具直伤 / 主角攻击三分量 / 充能分量),
; 敌打主角现行不查矩阵——主角 armor_type 轴仍休眠,轴生效待敌方技能攻击类型批。
; 实战读数(现行敌种甲配置):L1/L2 小怪全轻甲 → 穿刺类(开格充能/穿刺弹)×80 小幅折损、普通/魔法中性;
; L2 Boss 魔法甲 → 基础攻击与穿刺弹 ×120、魔法矢 ×50(不变)。重甲现行无消费敌种(零消费格)。
;
; 甲效矩阵加法核换装批(2026-09-18 用户拍板数值语言——小整数加法核,与 01-核心循环 数值语言条款同批):
; 量纲 percent→整数点数,三档 克制 +1 / 中性 0 / 被克 −1,九格 normal [0,+1,−1] / pierce [−1,+1,0] / magic [0,−1,+1];
; pierce×heavy = 0 中性为用户特别拍板(重甲挡刀刃不排斥穿刺;现行无重甲敌种,纯结构位)。
; 上方 2026-09-18 甲效矩阵用户调参批(五档 ×150/×120/×100/×80/×50)与更早三档三角批注的百分比量纲全部随批退役(历史留痕不删)。
; 键集 3×3 满格结构冻结不动;缺项回退 = 0 中性(battle_orchestrator._counter_delta);域 [−3,+3](schema 校验)。
; 奖励直伤行 payload 键 effectiveness 保留键名、值语义改点数(JSONL 契约稳定——analyze_playtest 等分析工具不破坏)。
+15
View File
@@ -94,3 +94,18 @@ price_a = 1.8
price_b = 1.1
relic_base_price = 15.0
spend_discount = 0.8
; GP+商店房实施批(2026-09-20 用户拍板,设计权威 design/research/proposal-gp-shop-room-2026-09-20.md 裁决记录):
; 掉落=均值±方差掷定(gp_drop 封闭流)+关级加值;精英行为预留行(当前无消费方,随精英节点内容批启用);
; 价格公式 = base_price + price_level_bonus(关卡) + price_increment × 该商品已购档数(已购档数在 冒险结构——
; 冒险内累计、跨冒险清零);商品三件全陈列无刷新无摇号(2026-09-20 拍板,提案 §2.4 的三选一+刷新方案退役)。
; 期望账:L2 商店前可得 GP 期望 ≈ 35–40(L1 全程 22 ± 惊喜 + L2 商店前五战 21 ± ),三件首购总价 L2 =
; (4+2)+(2+2)+(4+2)=16、全购欲望 ≈ 26–34——取舍空间成立;量级与方差全部 provisional-pending-曲线调参批
;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与Boss)。
; GP 商店房重构批(M-4 迭代 11 第②批 T1,2026-10-03——需求卡片 v1.3 全裁 P1 共享池/P4 ceil/P5 甲):
; pool_shelf 行族 = 共享池货架(kind_id 词表 attack/max_hp/armor/ap/feat_ticket/heal 六行——heal 系 T6 商店补回复〔默认原则:分类内成员都要在它该出现的渠道池里,现行缺〕、GP 袋负交易切边计价物不自售、feat_ticket 系 T5 专长凭证行〔非 Boss 渠道专长入口,价与其他商品相当=契约④口径②〕、
; 规则改写型特性三轴 doctrine 切边;库存=上限即库存,账本在 冒险结构 _bonus_taken 单点共享,行族不记库存);
; 价格公式换代(pool 模式)= ceil(base_price × b^(关序−1) × A^(本次进店已购次数)),price_a=1.8/price_b=1.1
; provisional 投值(零人工令改 provisional+后置追认,原 P5 甲研究拍板后置);products 行族 + 加法定价
; 随 legacy 模式(shop=off 对照承载)保留不退役,退役归 off 锚换代批。货架行值全部 provisional 归
; 难度曲线与Boss。
+59
View File
@@ -112,3 +112,62 @@ rows = [{
"damage_mult": 1.7,
"cooldown_beats": 3
}]
; 敌人技能池批(2026-09-16 用户拍板——设计权威 docs/proposal-skill-pool.md v1.0,全值 provisional 投值归 难度曲线与Boss):
; 五行 = 普通攻击 / 突刺(新行:伤害 ×1.5 冷却 2)/ 蓄力(增益技)/ 重击 / 硬化(新行:独占增益技——Boss 专属池载体)。
; 普通攻击两条域校验锁死:倍率恒 1、冷却恒 0(schema 拒绝配置)——池恒非空的结构前提。
; 蓄力 = 纯"攻击力增加"增益技(0 伤害、施加 empower 状态),与重击无任何组合语义(纠偏二——旧"蓄力→猛击"
; 固定配对随行为环退役);增益与倍率同拍只是两层乘法共存(×2 遇 ×2 = ×4)。
; buff_duration = 2 的口径:容器时限纲 = remaining_settlements(先兑付后衰减、授予当轮衰减段即 −1)——
; 施放发生在第 N 结算战斗段,需存活到第 N+1 结算战斗段才兑付("下一拍"),故持续 1 个有效结算 ⇔ 表值 2
; (提案"攻击×2 持续 1 结算"的容器语义落地;有效增益拍恰 1 拍,归 增益状态/难度曲线 复核)。
; 硬化 = 自防增益(fortify 状态 → 敌方防御池),Boss 池独占——增益 ≤1 纪律下旧蓄力拍不进 Boss 池(proposal §2.5)。
; 保底 M = pity_threshold = 4:连续 4 拍执行普攻 → 下拍必出特殊技(特殊技内按权重、不受冷却限制)。
;
; 分支节点图批(2026-09-17——L2 幽暗地域新技能行,全值 provisional 投值归 难度曲线与Boss):
; 撕咬 bite = 伤害 ×1.5 冷却 1(高频小爆发轮廓,与突刺 ×1.5 冷却 2 同倍率错开冷却轴——巨蜘蛛池载体;
; 复用技能行纪律下 lunge 仍归 L2 熊地精,两行不共用是有意的权重/冷却独立投值位)。
; 毒爆 venom_burst = 伤害 ×2.5 冷却 4(蛛化卓尔精灵 drider 专属池独占技——"Boss=专属池独占技+权重轮廓"
; 纪律的本批兑现;池引用唯一性 = 唯一 enemy_id: drider)。
; 两行均为 damage 型(存倍率不存固定值——伤害 = 施放者攻击基线 × 技能倍率,三乘法链第一乘)。
;
; 新效应词汇批(2026-09-19 用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0,全值 provisional
; 投值归 难度曲线与Boss):三词收敛两词——多段命中退役(无防御磨损体系,无对照机制);固定值伤害改形
; 必中附加分量(rider_mult,域 (0,1]):后置加法、必中全额、不进乘区不出暴击,分量值 =
; max(damage_floor, floor(攻击基线 × rider_mult))——随基线缩放保持共享技能表前提;
; ×0.75 价位锚 = AC 折扣档最轻档"不掉档的稳定伤害"(可掷分量期望 AC10≈0.80 / AC12≈0.76 对表)。
; ① 毒爆 + rider_mult 0.75(必中毒分量:atk3 → 毒 2 点,atk 区间 2–4 → 1–3 点随开战掷定值缩放)。
; ② 蛛网束缚 web_snare(新行,妨害型 debuff——对主角施加 off_guard 失衡):0 伤害施放行(不掷不折不兑付
; 伏击),字段复用 buff_ref/buff_duration;duration 2 = 1 个有效结算(empower/fortify 先兑付后衰减含
; 施放当轮口径);冷却 4;首发载荷 = 失衡(用户拍板),乏力 sapped 为第二词汇暂不实装;
; 池纪律 +:每池妨害 ≤1(ConfigDB 跨表校验);实装面 = 仅卓尔池,其余敌种不动。
;
; 第三关内容批(2026-09-20):大地脉动 earth_pulse = 灰矮人石君王 duergar_king 专属池独占妨害技
; (0 伤害施放行,对主角施加 sapped 乏力——第二妨害词汇首发,状态行 status_effects.tres 既有);
; 字段口径同 web_snare(duration 2 = 1 个有效结算);每池妨害 ≤1 纪律由跨表校验覆盖。
; provisional 归 难度曲线与Boss。
;
; Boss 半血解锁技三行(2026-10-05 技能池执行批 T5,迭代14④——D7 用户裁案甲:每 Boss 一行新独占伤害技,
; 工作值 provisional 归调参;pool 侧 半血解锁(trigger 收编)=50 门槛随 enemy.tres 三 Boss 行):
; quake_smash 震地怒击 ×2.5/CD4(食人魔酋长)· venom_curb 遏毒 ×2.0/CD3(蛛化卓尔精灵)·
; rockslide 岩崩 ×2.5/CD4(灰矮人石君王)。解锁语义/双面过滤/一次性弹出规格 = 敌人系统.md 意图呈现节。
;
; 有效倍率表(2026-10-05 技能池执行批 T2——D4 整数基线登记:敌技基线=int(round(atk×mult)) 四舍五入半值上取,
; 全伤害管线唯一非 floor 取整点〔豁免登记见 registry skill_table〕;半点恒上取保住 atk=1 特殊技辨识度):
; atk=0(区间下界):全技塌缩下限 1(突刺==普攻==重击==1)
; atk=1(L1 全系+L2 四行):attack 1 / lunge·bite 2(实际 ×2.0 +33%)/ heavy 2(==突刺)/
; venom_burst 3(×3.0 +20%)/ venom_curb 2(×2.0 =名义)/ quake_smash 3 / rockslide 3(×3.0 +20%)
; atk=2:attack 2 / lunge·bite 3 / heavy 4 / venom_burst 5 / venom_curb 4 / quake_smash 5 / rockslide 5(全精确)
; atk=3(L2 精英/L3 全系):attack 3 / lunge·bite 5(×1.667 +11pp)/ heavy 6 / venom_burst 8(×2.667 +6.7pp)/
; venom_curb 6(=名义)/ quake_smash 8 / rockslide 8
; atk=4(区间上界):全精确(4/6/6/8/10/8/10)
;
; 敌人表内容修批 N2(2026-10-13 用户令「按推荐方式」,迭代 24 卡①)——普通敌错峰两行(12→14 行):
; venom_spit 毒吐 = 伤害 ×1.6 冷却 2(巨蜘蛛族普通敌池载体,挂 (2,5)/(2,6)/(2,7),首遇教学位 (2,4) 不投——
; 「独占技随关错峰」首轨只做在 Boss 侧的补齐;venom_spit 自 (2,5) 起投)。
; shield_bash 盾击 = 伤害 ×1.7 冷却 3(灰矮人族普通敌池载体,挂 (3,0)/(3,5)/(3,7))。
; 两行均不带 rider_mult:L2/L3 普通敌 atk 1–3,floor(atk×r) 恒被 damage_floor 兜成 1,与 0.75 档效果
; 完全相同——白占字段(毒绕开护甲由 damage 直伤表达已足够)。
; 有效倍率(int(round) 半值上取):毒吐 atk1→2/atk2→3/atk3→5;盾击 atk1→2/atk2→3/atk3→5。
; 倍率 provisional 归 M4 数值批。池权重均 2;逐池「恰一普攻/增益≤1/妨害≤1」纪律复查过
; (巨蜘蛛族增益仍仅 charge、灰矮人族 (3,0)/(3,7) 仅 charge·(3,5) 无增益、两族妨害 0)。
+44
View File
@@ -95,3 +95,47 @@ rows = [{
"stack_policy": "stack",
"max_stack": 16
}]
; 减伤墙调参批(2026-09-13,playtest-2026-09-12 报告 Q1 触发——defense_scan 实测墙轮 39%、
; 真人局 12/12 轮零承伤满血冒险成功):defense_up magnitude -50 → -30——叠满两层 = -60 减伤帽
; (原拟 pool_clamp_lo=-60 被 schema 冻结域拦截,杠杆移至本表);石肤权重 24→16 随批(reward.tres)。
; 承伤带对 C3 F4(≤20%/普通场、≤50%/Boss 场)复验责任随批;B3 GDD "-50" 文字同步随下次 B3 修订批。
; 格面资源批(2026-09-15):新增 sapped(乏力——weaken 型负向,hero_offense 池,attack_up 镜像;
; 进 effect_pool_map 同批登记)——状态表六行→七行;enrage/off_guard 语义 = attack/defense 型负向
; (标错安全格 / 踩到危险格经极性路由兑付,不再绑"标错反噬"单一来源)。
; 护甲等级掷点批(2026-09-16 用户拍板——百分比减伤增益整体换装为 AC,无双轨):
; defense_up(减伤 −30 percent ×2 层)整行退役(上方 2026-09-13 减伤墙批注随之留档)——
; "叠满池触底 ×0"免伤墙的结构面随 AC 掷点从根杜绝;新增 ac_up(盾牌加护 +1 ac ×2 层——2026-10-12 由"护甲强化"改名)——
; unit=ac 新量纲(AC 阈值轴效果,不入 effect_pool_map 任何池),消费面 = 战斗结算掷点判定
; (防守方 AC = 主角表值 armor_class + Σac 型效果层数×幅度);off_guard(失衡 +25 percent,hero_defense 池)
; 保留不动,其余六行零改动——状态表仍七行(一行换装)。
; 敌人技能池批(2026-09-16 用户拍板——增益技新状态行,七行→九行;敌方增益走既有增益状态容器):
; empower(蓄力:敌方攻击 +100 percent,enemy_output 池——蓄力技 buff_ref 载体)与
; fortify(硬化:敌方防御 +100 percent,enemy_defense 池——敌方防御池新词汇载体,Boss 池独占硬化技)。
; 两行 duration = 2 的口径:先兑付后衰减含施放当轮 → 表值 2 ⇔ 有效增益拍 1 拍(施放后下一结算兑付,
; 提案"持续 1 结算"的容器语义落地——详见 skills.tres 批注);refresh ×1 层(冷却闸下不可能自叠加)。
; enemy 侧池语义 = side-wide(registry 既有口径):蓄力 +100 入 enemy_output 与 weaken/enrage 共池 clamp;
; 硬化 +100 入 enemy_defense 新池(乘区消费面 = 战斗结算 主角攻击落地段)。值全部 provisional 归 难度曲线与Boss。
;
; 数值压缩批(2026-09-17 用户拍板暂定):ambush magnitude 3→1(伏击附加 = 层数×幅度,3 整除 ÷3)。
; percent 全族(attack_up 50 / weaken −65 / enrage 25 / off_guard 25 / sapped −25 / empower·fortify 100)
; 与 duration/max_stack 全部不动(相对轴与结构轴不压缩)。
;
; 战斗数值结构修复批(2026-09-18 盘问会话用户拍板——本批三行全部临时值,provisional 归 状态层重设计批):
; off_guard(失衡)换装 AC 轴:unit percent→ac、magnitude +25→−2(失衡 = 主角 AC −2,更易被命中;
; 消费走 _defender_ac 既有 ac 型读法零代码;settlement effect_pool_map 同批删 off_guard 键——
; hero_defense 池清空、池结构保留);fortify(硬化)符号纠正:+100→−50(乘区 = enemy_defense 池 −50
; → 主角攻击 ×0.5 折半——旧 +100 实际 ×2 翻倍,与"硬化=减伤"语义相反,用户确认系符号 bug);
; weaken(削弱)叠层改续时:stack_policy stack→refresh、duration_settlements 1→2(同名重复授予
; 只刷新剩余结算数、层数恒 1——结构性消饱和,×2 叠满池触底 ×0 免伤轮从根杜绝;refresh 实现与
; empower/fortify 同款零代码)。duration 终值、off_guard N 值、当量锚复算全部归 状态层重设计批。
;
; 格面资源八型批(2026-09-20 用户拍板):① off_guard magnitude −2→−1(护甲型奖励/惩罚数值对齐 = ±1);
; ② ambush(伏击)行整体退役——群伤负向不再放大敌方伤害;③ 新增 alert(警觉:敌方护甲等级 +1,
; target_side enemy、unit ac、1 结算、stack 上限 16)——blast 型负向新载体,消费 = _defender_ac 敌方侧
; ac 型读法(本批新接线)。状态表九行→九行(伏击出、警觉进)。数值全部 provisional 归 难度曲线与Boss。
;
; 主角增益只续时不叠层批(2026-09-20 用户拍板「主角的其实也一样,都是按照叙事的方式,不要有叠层;只续时不叠层」——
; 对齐削弱 2026-09-18 续时定案):attack_up / ac_up 两行 stack_policy stack→refresh;
; duration_settlements=1 与 max_stack=2 表值不动(refresh 下层数恒 1、max_stack 不消费——weaken 先例;
; dur=1 下续时表现为重复拿重置当轮倒数,跨轮延续随将来 duration 拉长自然显形——数值归状态层重设计批)。
+11
View File
@@ -4,6 +4,17 @@
[resource]
script = ExtResource("1")
; 迭代 22 秘宝体系实现重构落地批 T1(2026-10-12):新目录 12 行全量换代(设计权威=秘宝.md「目录(重设计 v1)」;
; 旧 12 行删除——venom_edge/seal_burst 已随迭代 20 专长批迁出删除,本批实删 12 行)。三档=普通 7/优秀 3/稀有 2
; (传说留空=空集合法,迭代 20 追认③先例)。解锁混合制(契约 8 逐件分配锁定表,2026-10-11 用户裁 C1):
; 钥石购买件 unlock_keystone_cost(provisional 占位);条件件 unlock_requires 谓词行——计数键
; adventure_cleared=生涯冒险成功局数(C6 裁:打通整局冒险)/boss_kill_count=生涯 Boss 击杀/vision_used=
; 真视类工具累计使用(本批新增计数域)。全部数值 provisional 归调参批(契约 7)。
; 常时参数面件(无订阅行——效果=开局/装板参数注入,非事件订阅):阔视野带/储备罐/远程镐/信标网络/刷新练习/
; 刷新执照六件 effect_kinds 挂参数 kind,由注入面按装配集 kind 分发读取(_attune_refresh_x_total 先例)。
; 主动件:线香(battle 级充能)/天眼(level 级充能)charge_reset 扩签;试炼镐=自动兑付(charges=0 无上限,
; ap_charge_safe 满 charge_n=8 自动当轮 grant_ap——2026-10-12 反哺批⑤用户裁:无玩家发起入口、
; 旧行 counter_spend 受理闸随裁删除;挖格扣 AP 后、轮末判定前兑付;战斗级重置=账即时清空自然无感)。
rows = [{
"item_id": "morning_order",
"display_name": "晨操令",
+31
View File
@@ -74,3 +74,34 @@ rows = [{
"board_types": ["deep_underdark"],
"counter_items": []
}]
; 地形变异 首批类型定稿批(2026-09-19 用户拍板)——四行两轴×两关:
; 第一关 dungeon(平原/密林基线):荆棘丛(伤害型:挖开扣 1 点生命,轮末结算兑现)+ 藤蔓缠绕
; (信息型:挖开且显示数字时数字被遮蔽,呈现层读取);第二关 underdark(幽暗地域):岩浆
; (伤害型 2 点,登记名沿用)+ 烬覆(信息型)。unlock_level 门控 + board_types 交集门控双闸,
; 首关候选池恒 {荆棘丛, 藤蔓缠绕},第二关候选池恒 {岩浆, 烬覆}。
; counter_items 恒空:反制道具配套义务随框架契约保留,兑现归 Alpha 反制批(2026-09-19 用户拍板
; 反制道具改排 Alpha——切片期变异暂无反制手段,纯税状态已知悉接受)。
; 全部行值 provisional 归 难度曲线与Boss 调参批(effect_value/布置参数均为加法核小整数轴)。
;
; 第三关内容批(2026-09-20):伤害型关间递进 1(荆棘)→ 2(岩浆)→ 3(落石)+ 信息型矿尘;
; counter_items 恒空(反制归 Alpha 反制批);布量走既有公式
; (5% + per_level_bonus × 2——percent_of_cells 表头字段驱动,不加新配置)。
; 行值全 provisional 归 难度曲线与Boss。
;
; 基数修正批(2026-09-21 用户拍板):数量公式百分比基数由总格数改为候选池格数
; (安全格 − 初始展开区 = 实际可挖格)——总格数基数下初始展开占全板 56–67%,
; 有效密度膨胀 4–6 倍(L1 10×10 板实测 5%→30%,极端板全池覆盖);percent 语义 =
; 占可挖格比例,关卡递增由 per_level_bonus 绝对格数承载。表值不变归 调参批 重标。
; M-3 迭代 5 判读锁值批(2026-09-28):岩浆 effect_value 2→1 · per_level_bonus 2→0 · percent_of_cells 5→4
; (杠杆扫描 C3 组合收带 L2——配对种子读数见 design/qa/m3-iter5-lever-scan-2026-09-28.md;L3 侧效应=
; 变异密度 9%→4%、完美 L3 0→34%,L3 无带记观察态;行值 provisional→正式随迭代 5 出口人闸追认)。
; M-3 迭代 5 返工批(2026-09-29 U2 推翻 C3 裁 C1 两刀):percent_of_cells 4→5 回退;岩浆 1 与 per_level_bonus 0 维持——C1 终态;
; C1+v1 AC 组合读数归返工批全量确认(裁断全录 design/qa/m3-iter5-u2-gate-2026-09-29.md)。
;
; 屏蔽记录(2026-10-16 用户令):skewed_veins 贫矿脉行已删除——用户裁「地形变异特异关卡后续重新做
; 设计,先屏蔽」。删除后 Boss/精英图全局池空 → _resolve_global_variant 空行回落 → region 布置原路
; 兜底(battle_orchestrator.gd 既有回落路径),玩家面零横幅零角标零资源偏斜。删除行内容与首发批
; 裁决依据(2026-10-04 迭代 14 ③号卡:案A 资源面微贫向偏斜、effect_value=30 provisional、域 [0,50]、
; 0=中性 off 锚)见 git 历史;resource_face 机制代码(RewardEconomy 偏斜投影/schema 互锁/负样本
; 断言)全部保留,重建设计直接加行即可。
+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)
+56
View File
@@ -0,0 +1,56 @@
## 组合根 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 优先序(camp.md v2.3 定稿 2026-11-01 用户裁——有功能窗开着→先关最上层窗,覆盖卡⑤ 契约 9①
## 的 Q1 甲无反馈;设置开在浮窗之上仍无反馈=契约 3 保留):
## ①功能窗开着→close 最上层窗→①'设置开在浮窗之上→无反馈→②浮窗开→close(Q2 同键单义)→
## ③营地语境→open→④非营地→无反馈
static func on_esc_input(root: Variant) -> void:
var esc: Control = root.ui.modals.camp.esc_overlay
var window := _topmost_camp_window(root)
if window != null:
root.ui.modals.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿)
return
if root.ui.modals.settings_overlay.visible:
return # ①' 设置开在浮窗之上 → 无反馈(浮窗不隔层关闭——契约 3)
if esc.visible:
root.ui.modals.flow.close(esc) # ② 再按 Esc 关窗
return
if is_camp_context(root):
root.ui.modals.flow.open(esc) # ③ 营地语境唤起
# ④ 非营地语境 → 无反馈
# 五功能窗现开查询(camp.md v2.3 五窗互斥单开——模态栈深恒 1,至多一窗在开;生涯页入窗集)
static func _topmost_camp_window(root: Variant) -> Control:
for w: Control in [root.ui.modals.camp.forge, root.ui.modals.camp.treasure, root.ui.modals.camp.tree,
root.ui.modals.camp.codex, root.ui.modals.career_overlay]:
if w != null and w.visible:
return w
return null
## 返回主菜单动线(契约 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,88 @@
## 营地子界面模态壳基类(锻造工坊/秘宝匣/天赋树/奖励格图鉴四子界面公共形态):
## 实底全屏暖亮档+石纹暗角+顶栏(铭牌+钥石徽)+内容列+底栏返回钮。
## 开合可见性归模态栈(登记层开合一律走栈);返回钮只发 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:
# 半透明压暗遮罩 0.6(camp.md v2.3 定稿——场景根留底可见,窗口之下垫遮罩;交互聚焦由遮罩 STOP 兜底)
var dim := UiKit._modal_dim(self, Color(UiKit.DIM, 0.6))
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
@@ -18,6 +18,7 @@ var _keystone_badge: UiKit.KeystoneBadge # 钥石余额(badge 工厂件—
func _init() -> void:
# 全屏页壳(T6/T7 对齐批先例——暖亮底+石纹弱质感+暗角+56px 通栏顶条+内容列)
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, true)
page.base.color = Color(UiKit.DIM, 0.6) # 功能窗口化(camp.md v2.3 定稿——场景根留底+半透明压暗遮罩 0.6;观感随样张目检校准)
var top: HBoxContainer = page.top_row
top.add_child(UiKit.label("生 涯",UiKit.FS_TITLE, true, UiKit.WARM))
var top_sp := Control.new()
+1 -1
View File
@@ -173,7 +173,7 @@ func _draw_south_facade(canvas: CanvasItem, rect: Rect2) -> void:
var y_far := rect.size.x - far_h
var y_near := y_far - near_h
var y_hl := y_near - FACADE_HIGHLIGHT_PX
canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点)
canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.BRICK, true) # 口沿高光(合并归掩尘枚 BRICK——art-bible §4.1 换色合并①同值单点)
canvas.draw_rect(Rect2(0.0, y_near, rect.size.x, near_h), FACADE_NEAR_COL, true)
canvas.draw_rect(Rect2(0.0, y_far, rect.size.x, far_h), FACADE_FAR_COL, true)
+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
+16 -13
View File
@@ -59,7 +59,8 @@ func intent_probe() -> Dictionary:
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y)
# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y;
# name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口))
func name_line_probe() -> Dictionary:
var f := _efont()
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
@@ -69,7 +70,8 @@ func name_line_probe() -> Dictionary:
"armor_tex_bound": tex != null,
"plate": plate, "baseline_y": _name_baseline(plate).y,
"plinth_top_y": _pedestal_rect().position.y,
"plate_color": UiKit.HP_BAR_BACK}
"plate_color": UiKit.HP_BAR_BACK,
"name_font_size": UiKit.FS_HINT}
# ---- setter 面(presenter/组合根零改排) ----
@@ -332,15 +334,16 @@ func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2:
var icon_w := 24.0
if tex == null:
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var total_w := icon_w + 3.0 + name_w
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,12px 字形基线=中心+4)
# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,14px 字形基线=中心+5
# (0.35h 规则按字号档推导 14×0.35≈4.9→5,T0 实测校验——迭代 46 卡B §7.4 提档))
func _name_baseline(plate: Rect2) -> Vector2:
return plate.position + Vector2(4.0, 17.0)
return plate.position + Vector2(4.0, 18.0)
# 绘制内容矩形(世界局部;ui_struct_probe ⑥standee「不出框+下余量≥8px」断言读数面——
@@ -362,8 +365,8 @@ func _draw() -> void:
## 地面域:椭圆落影(圣经 §6.1 地台落影压脚:方向稿椭圆 (0,0,0,70),压在地台
## 上缘周围;24 段多边形近似椭圆,中心=台面上方 6px、横半径=台宽一半)
## + 石板地台(§6.1 近景地台独立件:TEX_STAGE_PLAZA 源 48×12 九宫格横向拉伸、顶缘受光线 2px
## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔STAND_STONE 族方向稿取色,几何同源
## _pedestal_rect——与 panel_skinned 缺位回退同构〕)
## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔板身 STAND_STONE/描边 PLAZA_EDGE/
## 顶缘线 BRICK——art-bible §4.1 换色合并①②改指目标色,几何同源 _pedestal_rect〕)
func _draw_ground(f: Font) -> void:
var sh_center := Vector2(box.x * 0.5, box.y - 24.0)
var sh_rx := (box.x - 36.0) * 0.5
@@ -379,8 +382,8 @@ func _draw_ground(f: Font) -> void:
_draw_plinth_stretched(plaza, ped)
else:
draw_rect(ped, UiKit.STAND_STONE, true)
draw_rect(ped, UiKit.STAND_STONE_EDGE, false, 1.0)
draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.STAND_STONE_RIM, true)
draw_rect(ped, UiKit.PLAZA_EDGE, false, 1.0)
draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.BRICK, true)
## 地台九宫格横向拉伸域(循环体提函数——左右 4px 端头切片 + 中段拉伸;纵向恒 12px 源档,
@@ -464,13 +467,13 @@ func _draw_armor_name_line(f: Font) -> void:
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
else:
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
match st._armor:
"ward":
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
"heavy":
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, col)
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col)
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, UiKit.WARM) # v3.5 暗底双色墨制
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制
+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
+14 -2
View File
@@ -185,8 +185,7 @@ const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
const STAGE_BG := Color8(62, 52, 42) # B 区舞台暗底(方向稿 render_battle_screen——battle_stage_view 面板底)
const BEAM_LIGHT := Color8(255, 236, 200) # B 区顶光柱暖光(用时乘 alpha 渐隐——battle_stage_view 光柱层)
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
const STAND_STONE_EDGE := Color8(90, 76, 58) # 站桩石板地台·描边
const STAND_STONE_RIM := Color8(196, 172, 132) # 站桩石板地台·顶缘受光线
const PLAZA_EDGE := Color8(76, 64, 56) # 小石台描边(art-bible §4.1 换色合并②合并目标——贴图已落此值,缺位回退同值)
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
@@ -248,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),
}
+79 -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,16 @@ 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() # 关闭被栈还原的选档层(动线序中间态——卡④ 换代点)
if not main.ui.modals.confirm_dialog.visible:
_problems.append("③篝火:二次确认弹窗未现(camp.md v2.2 必须先确认)〔营地图批 V7〕")
main.ui.modals.confirm_dialog.confirmed.emit() # 二次确认离场(confirmed 先于关层——卡④ T4)
await _frames()
_expect_exclusive("③营地→菜单(回退)", "菜单")
@@ -229,3 +232,73 @@ 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〕〔ESC批 B1〕")
_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.tree.visible:
_problems.append("[esc] 子界面开态 Esc 未关窗(camp.md v2.3 定稿——先关最上层窗)〔ESC批 V1〕")
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("[esc] 关窗同拍唤起浮窗(先关窗后浮窗——两拍应分离)〔ESC批 V1〕")
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批 B2〕")
# 设置开在浮窗之上→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〕〔ESC批 B3〕")
# 返回主菜单动线(契约 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〕〔ESC批 B3〕")
# 非营地语境(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〕〔营地图批 V4〕" % [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〕〔营地图批 V4〕")
# 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〕〔营地图批 V6〕")
for l in atlas_locks:
if l.get_theme_color("font_color") != UiKit.STONE_DEEP:
_problems.append("营地灰字:图鉴锁文未用 STONE_DEEP(wc4 :167)〔句柄子容器批 V1〕〔营地图批 V6〕")
main.ui.modals.camp.codex.closed.emit()
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〕〔营地图批 B1〕")
# 有断点三出口(选路停点形注入——同开场行③ 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 第五路同吃此框,确定性禁依赖默认分支)
+109 -17
View File
@@ -39,36 +39,118 @@ 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〕")
var esc_src := 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_src.is_empty():
problems.append("[esc] 零动效扫描面为空(源文件读失败——非空前置失守)〔ESC批 V7〕")
# 退出项接线断言(卡⑤ V5——绑信号连接与调用链;headless 不可真退=静态判据+观测面局限申报)
var quit_wired := false
for c in main.ui.modals.camp.esc_overlay.quit_requested.get_connections():
if c.callable.get_method() == "_on_esc_quit":
quit_wired = true
if not quit_wired:
problems.append("[esc] 退出钮接线断链(quit_requested 连接目标≠_on_esc_quit)〔ESC批 V5〕")
var quit_chain := FileAccess.get_file_as_string("res://presentation/main.gd") + FileAccess.get_file_as_string("res://presentation/main_esc_flow.gd")
if not quit_chain.contains("MainEscFlow._on_quit(self)") or not quit_chain.contains("root._quit_game()"):
problems.append("[esc] 退出调用链断裂(_on_esc_quit→_on_quit→_quit_game)〔ESC批 V5〕")
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 +476,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 +598,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 +801,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
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff. Show more