Compare 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 94628fca83 工程+文档(车道恢复批·worktree铁律回归): 车道恢复2026-10-26前默认——主工作区只作合并落点,一切改动走独立worktree(建区即setup_worktree_userdir,收口=冻结→件核→并master→重塑→主区merge --no-ff→CI全量绿→删区,CI红回任务worktree修复合回);pre-commit直改闸随主区直改车道退役(tools/hooks/pre-commit删除+run_all_gates.sh摘在位自检段与--staged增量档,闸清单/域划分/判据零动);CI三件(ci_runner/post-merge/post-commit)零改动,验证模型不动;执法移交agent侧worktree-guard(本机件,合回后主区随动换代);规则四份随动: process-conventions §1四条重写+§2直改序列句+§3.4一切批建区+§3.8验收费注,AGENTS车道铁律/运行与验证/改动四类/五段铁律/规范导航行,architecture车道执法节(删pre-commit行+守卫职责换代+合回闸残句订正),test-conventions §4主区直提句摘除;孤立验证=worktree内--docs手动档全绿(06-08e+10),件核grep主区直改/直提/pre-commit仅剩退役凭据注记与豁免面说明 2026-10-31 18:34:09 +08:00
DKChan 870b2f2bf6 写卡(营地交互重做·卡④重立+卡⑤信息补充批): 卡④ v2.3非有效卡→v3.0现行有效卡+卡⑤ v1.3补齐2026-10-18后新规+待办清单状态回写+README营地能力行9行
卡④(2026-10-31 用户令据 req-card 第〇步重立):v2.3 非有效卡→v3.0 现行有效卡,重立判据已齐(美术设计已闭合=样张过审 2026-10-31+art-bible 条款版本 v3.24 §6.2),零设计待裁点;10 层拆分口径(bg/wall/八物件)、七格验证闸、落位与可复用性预检、实现规范预算、独立验收审计、能力行全备;P1–P5 与 §5 五待裁点归位为代码段 /iter 开工门(不阻塞编迭代)。卡⑤ v1.2→v1.3:按 2026-10-18 后新立规则补齐七格闸/落位预检/预算/审计,删「变异验证伴生」与「审查回写要点」变更叙事(gdd_lint R4),跨卡接口保持既有名。待办清单卡④条目状态回写:待美术设计→美术设计已闭合、缺口属性→代码段开工门、实况与卡指针改 v3.0。README 表现与支撑段补 9 行营地能力行(卡④7+卡⑤2),真实入口按能力审计规则补类.方法()空括号、代表用例删 .py 片段。

linter:文档域全闸 run_all_gates.sh --docs【验收通过】闸况与基线一致无新增红;08e 能力审计红项 6→0(补类.方法()凭证+删 .py);pipeline_gate PASS、gdd_lint 19 文件 0 不通过。

本提交同时收编工作区并行会话待提交改动(一并入库避免丢失):design/gdd/systems 5 篇(奇物/地图战斗资源/增益状态/战斗结算/行动经济)、提案-弃图刷新系双长流报告、迭代 46 卡C 停机呈报。调研-AI时代工程实践检查点系中途过程稿(文件自述吸收即死管辖),不随本批入库、留工作区。
2026-10-31 17:14:01 +08:00
DKChan 3a1878ff80 文档(画图技能换代批·用户裁): AGENTS.md 图优先载体句换代——会话图统一走 diagram-design 技能(用户级安装 v2.6,45 图型,自包含 HTML+SVG 编辑级设计系统,自带 self_check 自检),大图精修与导出走 archify,无内联渲染退字符画;diagram-design 经 GitHub 下载安装于用户技能目录(.zcode 不入 git),原禁用句整句删除;2026-10-31 用户裁「用 diagram-design 替代」 2026-10-31 17:01:19 +08:00
DKChan d216ae0498 工程(表批注清理批): 18 张数值表删全部 ; 批注 617 行——沿革只进 git commit log(doc-conventions §5 禁写变更记录,2026-10-20 禁写变更记录批「约束句保留」判据漏网面补清);批注中现行语义句逐条核对 owner 文档(保底/行序契约/行键带键/四池乘区/判定式/商店定价/AP 节拍不变式/修饰位空表/效果引擎词表/hobgoblin 缺口登记/conversion 标定/秘宝常时件等全命中不迁移),真缺口两处迁 owner 文档:敌人系统.md 补敌技伤害基线取整规则(四舍五入半值恒上取·全伤害管线唯一非 floor 取整点)+战斗结算.md 明细流水补奖励直伤行 effectiveness 键名与整数点数量纲的分析工具兼容契约句;待办清单登记「状态合并改累加制」条目(2026-10-31 用户立案:同名状态重复授予由刷新重置改时长累加;查证设计文档/reports/git 全历史无既往累加制决策,属新裁决非补同步;待文档设计);纯注释删除零行为变化;待办清单与战斗结算按 hunk 粒度暂存——仅本批改动入库,并行会话在改面留工作区不随本批提交 2026-10-31 16:30:52 +08:00
DKChan e090ea8af2 文档(会话图载体禁Mermaid·用户裁): AGENTS.md 图优先载体句补显式禁用——对话输出禁用 Mermaid 代码块(退路只有字符画),本条优先于 visualize 工具内建的『静态节点连边图用 Mermaid』建议——禁用句而非默认句的原因=工具默认会钻空子,2026-10-31 用户裁;文档侧 doc-conventions 的 Mermaid 网页发布保底未动 2026-10-31 16:30:17 +08:00
DKChan 263050adf1 账面(迭代46拆批换代·用户裁): roadmap迭代46行三卡→两卡(移出棋盘格边长口径收口批——L3#2停摆退回写卡段)·周期9.125→8.625·状态待开工→进行中·当前位换代(A/B收口待合回+卡C停机呈报指针)——用户授权代写,机械面换代非本实现批产出 2026-10-31 16:18:22 +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
DKChan b5b2c55557 docs: 迭代46定义已入库 2026-10-31 07:25:42 +08:00
DKChan e153e8a3c3 美术(战斗区背景入包批): 三关背景与共用地面终稿入包 game/assets/art/scene/——作者选定 dungeon v10c/underdark v10c/deep_underdark v10c/ground v12b;背景 1280×252 不透明 32 色(实测平均 L 0.0148/0.0132/0.0111),地面 1280×100 顶缘透明 32 色,按作者裁保留原亮度(不透明域 L≈0.132,低于 §2 世界中层带 0.22–0.45,登记为作者例外);art-bible v3.29→v3.30:§6.1 新增「B 区舞台背景与共用地面的交付绑定」条(尺寸/色数/alpha/读数/例外指向)+ground_h 终稿高 100 回填句+资产渠道句现状化;art-status-checklist 增交付行、art-assets-checklist 现状句换代、asset-path-map scene 行区分「战斗背景已入包/营地仍登记超前」;battle-stage-prompts v1.7→v1.8 状态句换代;待办清单实况记选定与读数并保留 STAGE_BG 随代码批消化;入包对应代码侧需求卡片在册(board_type 选图/共用地面层/ground_h 落表/四柱光柱与 STAGE_BG 退役待迭代编排);验证=path_check 0 问题+pipeline_gate PASS+文档域闸全绿 2026-10-31 06:47:29 +08:00
DKChan 770bfe62ae 订正(账面): 当前位下一步换代——迭代46已由并行编排提交da24047c编入三卡(换图系双基座七件批+站桩名字行提档与地台色合并批+棋盘格边长口径收口批,≈9.125工作日),本笔iter45收尾提交行文时该编排未入眼,「两卡入编排待发令」过时;改=iter46待开工待用户iter发令,待编池不动 2026-10-31 06:43:26 +08:00
DKChan 457e073901 收尾(迭代45批后收尾): 反馈点七条处置闭合——①architecture落文六条: breakpoint_store行现状化(已建件-迭代45,构造句改述=九引用束解包原名成员保迁移函数体逐字+resume_adventure留宿主壳与宿主侧回填语句按R2所有权缝分家+实测类变量15贴顶禁再增)+board_hover行现状化(引用束实为7项=三引用+四Callable,卡片预估5项偏差实况在案)+单格两件与站桩两件两行现状化(成员数12/13与25/12/11及宿主30→10/19→10与实现一致零改述)+gdstyle计数截断缺陷节补清偿结果(两件已随迭代45真实清偿且基线refresh收账,上游报issue仍在册);第6条adventure_runner/board_view键账终值半=基线已在册承载(31/23留债收账归refresh批已完成),architecture无对应计数行零落文面;②第7条工具观察迁待办新条(run_all_gates 02关联档多套件词传参与run_all选跑解析器不兼容——两修法待用户裁决,直改类待执行);③目检三项登记体验验证清单㊸(悬停三语义抽验/站桩意图三档与三态观感/单格六步时序整帧零预期差异确认);④产出物退役九件(吸收即死+证据只留最新): iter45实现报告/反哺点/验收审计/两停机呈报+呈现层状态件批/gdstyle尾波补件批两卡+单格绘制状态件/站桩状态件两设计过程稿——判据全过: 反馈点七条全处置(1-6落文/7登记),遗留义务全迁移(键账终值=基线在册/02传参=待办新条/目检三项=㊸),引用面=roadmap行保留字+尾波条目随整删出清;⑤账面: gdstyle尾波补件批条目整条删除(两卡合回+键账留债收账在册,状态机出口),roadmap迭代45行状态已完成+卡片/报告两列退役保留字+当前位换代(迭代1-45全完成,下一步=两卡入编排待用户发令) 2026-10-31 06:41:41 +08:00
DKChan da24047c51 编排(迭代46): 三卡编入——换图系双基座七件批(8.125天中值,凭据=奇物v1.3+行动经济v1.3+战斗结算v1.4+地图战斗资源v1.3)+站桩名字行提档与地台色合并批(0.5天,凭据=art-bible v3.29)+棋盘格边长口径收口批(0.5天,无设计线段实况证据指认);卡序照用户点名1+3+4抄写,合计中值9.125工作日居5-10常规区间零特批;roadmap迭代46行待开工+三待办条目推进已编迭代46(交叉核4链);四查全过(三卡盘上在/凭据齐/估时9.125/流水线闸绿) 2026-10-31 06:32:26 +08:00
DKChan 78c8f83419 文档(载体默认换代批·用户裁): 图优先载体默认 HTML+SVG——AGENTS.md 载体句换代(默认 visualize 技能 HTML/SVG 内联渲染·流程结构图与空间对比像素示意同一通道·无内联渲染退 Mermaid·再退字符画)+doc-conventions §3 流程行换代(默认文档内嵌 HTML/SVG 表现力优先·图体行数计入节点篇幅·过滤型网页端改 Mermaid·无渲染环境字符画)+节点内顺序句随改;2026-10-31 用户裁,理由=表现效果更好,接受网页端过滤内嵌 SVG 与 git diff 可读性代价 2026-10-31 06:15:08 +08:00
DKChan e34dceb1a9 文档(图优先表达批): AGENTS.md 回答风格规则加图优先小节+格式优先级换代——判据=多实体明确关系(顺序/包含/依赖/状态转移/布局对比)内容直接画图不写长段,单点事实与纯数据不画;载体=节点连边图默认Mermaid代码块·空间对比与像素级示意走visualize技能HTML/SVG·无渲染查看端退回字符画;边界=硬数值与前提留文字·图不承载唯一数值依据·图文禁重复;doc-conventions 表达分工同步扩(§3流程行扩「字符画或Mermaid」收编结构关系类,复杂结构图优先Mermaid源码即文本,§2箭头链句与节点内顺序句随改);依据=cnoe-io/LMCache/vercel-labs三份AGENTS.md在案条款+Anthropic系统提示词artifacts图示必须可视化规则,2026-10-31 用户裁 2026-10-31 06:04:46 +08:00
DKChan d478cf70ea 写卡(站桩名字行提档与地台色合并批): 需求卡片v1.1定稿零待裁点——名字行五取值点FS_CAPTION→FS_HINT+基线0.35h重算(牌顶+18)+probe增name_font_size键/STAND_STONE_RIM·EDGE两常量退役改指(受光线与口沿高光→BRICK,描边→新PLAZA_EDGE承载#4C4038)/golden板面三张申报重锚(§3申报制);断言面V10-V12三锚三件(probe实读+源码锚+FS_CAPTION计数锚==1+常量表经load().get_script_constant_map——类引用直调parse error grilling实测)/V6基线149→150申报+run_all.gd注释换代;grilling复核三阻断两建议全落(V11写法parse error/三测量点缺判据/三绘制点改指缺正证/对照表对应句/结构图漏行);能力清单补行落表(迭代43移交小件①了结——站桩呈现定值与素材绑定行+覆盖域表战斗表现行,README工作树同批含并行会话换图系卡能力行五行顺带落地);待办清单两点条目+已写卡·移交小件①落位;估时0.5工作日 2026-10-31 05:59:52 +08:00
DKChan 789b15f34f 设计(迭代43批后待裁两点): 名字行提档FS_HINT 14归六档+换色合并三处拆解——§7.4小字号豁免条款补站桩名字行句(必读信息不入②装饰caption在册面,几何随档联动,验收句扩两绘制位取FS_HINT)+§4.1换色合并登记句换代(①地台顶缘受光线②地台描边两对照合并执行=绘制点改指目标色+源常量删除,③舞台底对改注随§6.1舞台背景插画批一并处置退役或改指禁单独改值出批,均2026-10-31用户裁);待办清单两点条目闭合(文档设计已闭合,凭据=art-bible v3.29入实况,缺口删除)+战斗区背景图替换条目实况补STAGE_BG随批消化句;v3.28→v3.29 2026-10-31 05:18:31 +08:00
DKChan 9eedf8965d 收尾(迭代44批后收尾): 反馈点六条处置闭合——①architecture落文六条: ui_registry/RoundCycle/BattleFacts三行现状化(已建件-迭代44,四件类变量3/9/5/14与ModalFlow随容器构造实况,两新件构造句按实态=三参直构/零参直构,实测值8·3/15·4换代)+装配节两句实况(ModalFlow先在座/shot与fx_shot取证探针随批改址)+main_ui_build行层序事实源句改指ui_registry.LAYER_CONTRACT+ui.apply_layer_contract+S13测量面注销句现状化(收尾现行形态=run()尾每批一次,申报剥离凭据215da315/v1.1重裁在案)+S13豁免第三条注销句转已实施(宿主实测15);附带main_overlay_flow行模态口指址刷新(root.ui.modals.flow.open/close——与头注词面修复合拍,验收审计缺漏2同族);②gdstyle基线refresh收账(--refresh --force,16→5键): 消失=main.gd49/orchestrator38+文件长1035/tune_sim await/board_cell_unit30/standee_body19五键清偿+iter42归绿6键离账(board_view allocation+_paint_cell三键/level_runner _enter_node),重登记=留债三键按实测28→23/32→31/50→33,零新增键(布局批新码gdstyle全净);③目检两项登记体验验证清单㊷(三屏切换与模态开合/战斗一局全流程观感);④产出物退役十件(吸收即死+证据只留最新): iter44实现报告/反哺点/验收审计+组合根注册面/战斗编排归约/测速循环三卡+组合根注册面/战斗编排类变量归约/测速循环去await三设计过程稿+grilling五卡复核——全部判据核过: 反馈点11条全处置(1-6落文/7 refresh本笔执行/8条件性观察无未决随本笔备案/9净零/10被条目删除覆盖/11迁待办新条),遗留义务全迁移(目检㊷/refresh基线/头注已修/死preload登记),引用面=roadmap行保留字+战斗域条目改git提交号;⑤账面: 零豁免五键清偿批条目整条删除(五键全清+两迭代合回+refresh收账,状态机出口),战斗域结构深拆批条目净零重立(宿主瘦身子项完成记实况引bee73db5,原卡片引用随退役改git提交号——多子项条目与卡片死期规则的闸机结构冲突按net-zero先例解),main.gd死preload八条新登记(直改类待执行),roadmap迭代44行卡片/报告两列退役保留字+当前位换代(迭代1-44全完成,下一步=iter45收尾) 2026-10-31 05:12:31 +08:00
DKChan 4b174877be 补提交(布局几何单点化批·代码面+工具面+architecture行·跨会话遗留): battle_layout.tres 新表单行五字段(stage_h/info_panel_w/ap_bar_h/log_band_h_dig/log_band_h_settle)经 schema battle_layout_table.gd+域容器 config_domain_presentation.gd 收进 ConfigDb.presentation(ConfigDB 三容器→四容器,manifest 增 battle_layout 键,B区高度裁决 2026-10-30 用户裁=取代码标准 stage_h);views 五件构造签名增 layout 参(BattleStageView/HeroInfoPanelView/BoardZoneView/ApBarView/BattleLogView 常量高度改表值驱动)+ui_assembler 装配传 layout+probes 两件改址+main.gd 结算带注释去数值;tools/layout_config.py 新件+render_sample_sheet 两版读布局表现算(样张工具面);文档面: architecture.md ConfigDB 行换代(四容器成员 7/7/5/1、类变量 10)+HUD框架.md 三区表/装板不等式/调参表去数值化改指布局表+数据配置.md+interaction-patterns.md 明细带句去数值(battle.md 区表与待办清单登记面已随 1cdef6ba 在库);消费漏网 L1 就地补改九处(构造签名换型与 iter45 断点件收编的测试改址漏网,判据零变化): theme_projection_test:386/player_copy_sweep_test:44,98/adventure_feats_test:772/standee_fx_sweep_test:75/feat_vouchers_test:292 六处补 layout 实参,adventure_feats_test:791/level_shop_economy_test:1480/feat_vouchers_test:81 三处断点成员直读改经 a._bp_store(iter45卡②六成员搬移漏网,02 闸合回后首全量暴露);load_tables 超函数长键清偿(容器装配块抽 _assign_loaded 私有协函数——表行按域收进四容器+resolver 注入单一职责,与 _cross_validate 委托先例同型,S13 新增红修至基线口径内);窄验: 四件修复套件单跑全绿+gdstyle 基线外新违规 0 2026-10-31 05:05:08 +08:00
DKChan 39907d82f6 工程(闸机提速批·随动件入库): ①run_all_gates.sh 文档域增量档本体已由并行会话 a7c63850 先行入库(同内容零冲突),本笔补齐其随动四件——ci_runner.sh 回执采信修正(回执 commit 与在跑尖端对齐才采信红项,不对齐写 note 标记:闸起手自检即中止时不写新回执,盘上旧回执当本次红项会撒谎——2026-10-31 实跑缺陷修正)+规约三份随动: AGENTS 改动四类与五段铁律的直改通道句收敛(『登记在案后由用户发令』→『由用户发令』),process-conventions §2 同句与写卡段配套维护句同步,doc-conventions §12.1 直改序列去登记段+条目定位句换代(『现在不做、以后才做的事』登记一条;条目记录走到哪一段,不是各段的开工前置) 2026-10-31 05:03:38 +08:00
DKChan f76c878e4f 补提交(跨会话旧报告欠账+uid边车): 四份在册引用而未跟踪的过程件入库——盘点-验证与清单体系-2026-10-28(接收方=待办清单『验证与清单体系整改』条目,阶段0盘点明细)+审计-全库架构审查-2026-10-29(接收方=待办清单『架构审查优化讨论』条目,七节读数)+工程改动报告-存量断言精简C档-2026-10-28(接收方=同条目阶段2闭合记录,6组33→7+冻结清单对账1587→1561)+评估-美术资产清单与性能预算-2026-10-30(接收方=待办清单卡④条目『评估报告同步』句,美术师交付物两章合并评估,未改权威文档);tune_sim_batch_parity_test.gd.uid 边车补齐(iter44卡③对拍套件.gd 在库而.uid漏提交——.gd/.uid同步纪律) 2026-10-31 05:01:33 +08:00
DKChan 21c4a5e915 美术(战斗区背景+营地场景图两线文档批·跨会话遗留入库): ①战斗区背景线——art-bible §6.1 场景构成换代(背景层=三关变体 scene_stage_bg_dungeon/_underdark/_deep_underdark 按 board_type 选图不走 tint,顶部空区+纵深洞厅远景三关母题地精/卓尔/灰矮人,不画近景地面不画光柱;连续地面层 scene_stage_ground 读 ground_h 三关共用;光柱不迁入背景,资产到位后四柱程序层直接退役)+battle-stage-prompts.md 新件入库(分层生图提示词卡,三变体+共用地面,依据 art-bible §6.1/§6.3/§4.1+布局表 stage_h/ground_h)+art-prompts §5.1 转指路+asset-path-map/art-assets-checklist 同步+art-pipeline-tools.md 新件(美术后处理与校验工具清单——tools/ 下像素化/校验/色板脚本用法,与 art-prompts/camp-scene-prompts 分工=出图与后处理两段)+待办清单战斗区背景条目实况缺口换代;②营地场景图线——2026-10-31 用户裁样张过审(保留全部待选稿,各素材第一张样本稿为终稿),art-bible §6.2 十张分层条款落文,camp.md v2.1→v2.2(scene_camp_bg 背景层 z0 不透明全幅+scene_camp_wall 围墙层 z1 门洞留透明缺口透出背景层小路+八物件层),camp-scene-prompts §2.3/§4 现行坐标与分层验收事实源,提案-营地场景图美术生成方案补现行口径指针(2026-10-22 讨论口径留痕不改,执行以五份现行文档为准),待办清单卡④缺口换代(P5 落文销号,样张过审+条款版本两项齐;余 P3/P4/§5 五待裁点/终稿后处理未清);在途图归位 artwork/2026-10-30-战斗区背景/(vibe_images 越界目录已清) 2026-10-31 05:01:06 +08:00
DKChan 2bc82febd3 文档(换图系双基座七件落文·跨会话遗留入库): 换图系专长流派设计定稿落四份 owner 文档——奇物 v1.1→v1.2(事件族增换图系第六族+设计原则补基座先行/行为改变检验两条+feats.tres 30行实数换代=基础27+迁入2+规则豁免1)+行动经济 v1.2+战斗结算 v1.3+地图战斗资源 v1.2(七件=掘进储能/折价换图/弃图轰击/换图蓄势/换图庇护/弃图休整/弃图回收;双基座可叠加,后续效果在本轮五段结算内兑付,弃图回收按旧图探索进度发清图奖励);game/tests/README.md 能力行六行入库(换图系五行=refresh_feats_test.gd 计划测试锚定,代码未随批未建;另布局几何单点化批的『战斗屏三区几何与棋盘格边长』覆盖行+覆盖域表 HUD框架 行扩句随同文件搭车——其代表用例 board_gen_reward_test._smoke_layout_cell_formula 系棋盘格边长口径收口卡计划测试);提案-弃图刷新系专长流派 v0.4 落档(接收方=待办清单『封锁-蓄力combo设计素材』条目,用户裁保留为写卡参考凭据) 2026-10-31 05:00:37 +08:00
DKChan a7c63850b7 工程(提交点文档域增量化批·pre-commit 触发粒度): run_all_gates.sh 增量档——--staged <清单文件> 仅配 --docs,触发矩阵唯一落点(任一 .md→06/06b/07/08/08d 先按清单内 .md 扫描·待办清单/roadmap/reports→08c 全量·systems/tests-README/reports→08e 全量·10 恒跑),快路径红必全量复跑定谳(清单面外红不入判定面,与 CI 全量严格对齐),未触发闸记 SKIP 入 not_run;user 目录隔离收窄至代码域(文档域不启引擎省 0.8 秒);pre-commit 钩子收暂存清单(quotepath 关闭保中文名,NUL 转行落临时文件)传 --staged,清单建不起回落全量档;不带 --staged=现行全量档 CI 零影响;窄测三态过(纯代码全 SKIP 6.2s/登记面 08c 触发+面外红定谳注记 7.9s/设计文档快路径 7.3s),Windows 瓶颈=python 进程启动,实际省 1-3 秒;横幅钩子与 config 注册不入 git(.zcode gitignored)随盘生效 2026-10-31 04:28:03 +08:00
DKChan 654aa73986 修复(迭代45·CI追绿): resume_adventure超函数长度键清偿——壳吸收宿主侧回填语句后55行超默认50,把续玩收束段(停点直回/已兑付重发/无停点续玩起步19行)按原语句序抽为宿主私有_resume_pending_or_start,壳降34行语句序逐句不变所有权零变化;干净树复验三断点套件全绿+S13零新键;CI同轮02/09另两红经干净树分离判定系布局几何单点化批未提交代码脏改,非本批面(实现报告§6.15) 2026-10-31 01:47:26 +08:00
DKChan a86a8dc367 批件(迭代45): 实现报告/反哺点清单/验收审计报告/两停机呈报五件落reports——对照表V1-V4+补件V1/V2全绿·实现决定记录15条·键账终值留债口径(30→10/19→10清偿,31/27/23留债)·能力行核对零回表·目检登记三项·审计通过零缺漏;接收方=roadmap迭代45行与两登记条目 2026-10-31 01:47:06 +08:00
DKChan 1cdef6bae3 文档(布局几何单点化批·文档面与登记面直改): battle.md 区表/线框/明细带去数值化——分区几何唯一事实源改指 battle_layout.tres 五字段,算例表退役改「算例不入文档」句并清除 .py 路径引用(08e 判据清偿);待办清单登记面——iter43 条目三点→两点换代(B 区高度已裁并落地记载在实况,标题来源字段恢复裸值=08c 判据清偿)+战斗区背景条目改读布局表+换图系双基座七件条目状态推进(文档设计已闭合+已写卡)+清单头句对齐「条目记进度」口径;提交点文档域闸八闸全绿,CI 在案红由 post-commit 自动追绿复验 2026-10-31 01:30:10 +08:00
DKChan f817ab2ddf 合并(迭代45): 呈现层状态件批+gdstyle尾波补件批两卡合回——卡①单格两绘制域件(cell_base_layer/cell_mark_overlay)+站桩两状态件(standee_vitals/standee_motion)建件·宿主30→10与19→10真实清偿·探针u.face改址(含设计清单漏计第五处L1修复)·golden三张0.00%;卡②board_hover悬停域件+breakpoint_store断点域件(R2整族归件,v1.1用户裁)·27键冻结·键账31/27/23留债;独立验收审计通过零缺漏;两停机呈报裁决链完整(L3③潜伏红定谳绿=T0实跑十八断言绿·前提已被413124b0消解/规格缺口R2裁决) 2026-10-31 00:33:42 +08:00
DKChan 9b0b164d38 iter45-卡2:board_hover+breakpoint_store拆件(R2整族归件,27键冻结,键账31/27/23留债)〔补件V1-V2〕 2026-10-31 00:33:02 +08:00
DKChan e7920e8fe0 iter45-卡1:单格两绘制域件+站桩两状态件+探针u.face改址(golden三张0.00%,键账30→10/19→10清偿)〔状态件V1-V4〕 2026-10-31 00:33:02 +08:00
DKChan b9546e7b69 注销(待办): 本地CI化改造条目整条删除(net-zero)——批已落地并首跑验证: 机件四件(ci_runner/post-merge/post-commit/pre-commit)+guard瘦身+规约四份换代随 c91bbddd 在库;CI首跑读数=master尖 c91bbddd(含并行会话合回的迭代44三卡 bee73db5)verdict green 零红闸 398秒,ci-status.json与ci-history.jsonl流水账在案;卡片欠账补提交 7a2d19fd 修掉 08c 源头(并行会话合回时唯一在飞红);post-commit 钩子在本批两笔提交点实走无状态零触发路径符合设计,post-merge 首个真触发=iter45合回 2026-10-30 16:59:07 +08:00
DKChan c91bbdddee 工程(本地CI化改造批·验证模型换代): 合回后跑测试制落地——①机件四件: tools/ci_runner.sh(merge=合回验证点对master尖无条件全量/commit=仅CI红态追绿;锁mkdir+在跑尖端判定+rerun标记=跑完补跑coalesce连发触发只测最新尖端;起手--editor --quit刷类名缓存接管原合并后零复跑动作;读闸回执写ci-status.json+ci-history.jsonl红闸与关键日志行在案)+hooks/post-merge(主工作区merge落地即触发,worktree内合并不触发)+hooks/post-commit(红态追绿触发)+pre-commit换代(撤代码面拦截=主区直改全面放开,合并收尾提交MERGE_HEAD豁免,保留秒级文档闸);②guard瘦身: 合回闸回执六况校验与主区代码面Edit/commit拦截退役,留--no-verify拦截+iter数卡校验+规则闸;③run_all_gates.sh头注换代(强制验证点=合回后CI全量,手动档降开发期)+.gates/.gitignore收CI产物;④规约四份换代: AGENTS(运行验证首条/规范导航行/车道铁律/交付验收红线/改动四类)、process-conventions(§1四条重写=主区直改车道+合回后CI+worktree降为大批约定+执法三层,§3第8/9条=CI验证载体+重跑语义「同一份字节禁止第二次人手跑」)、test-conventions §4(开发期窄测+合回CI+追绿)、iter技能五处(闸域/T0/闸序列/总收口/坑速查);⑤待办条目登记。设计讨论会话内闭合零待裁点,用户已发令;收口=迭代44合回CI首跑 2026-10-30 16:48:00 +08:00
DKChan 7a2d19fd12 补提交(写卡段欠账): 需求卡片-棋盘格边长口径收口批-2026-10-30 入库——待办清单:326 引用在先而文件一直未跟踪,主区盘上存在故 08c 流水线闸查盘放行,worktree 冷检出无此件即红(iter44 收口期 08c 红的成因);跨区失配随本地 CI 化自然消解(CI 在主区跑,盘上=库内同一面),本件补齐引用实体的历史在库 2026-10-30 16:47:20 +08:00
DKChan bee73db51b 合并(迭代44): 组合根注册面批+战斗编排归约批+测速循环批v1.1三卡合回——S13零豁免三键真实清偿(main.gd 49→15/battle_orchestrator 38→15/tune_sim裸键消除)·批末全量域闸13/14绿(02/05/09三闸从基线红改善归绿·唯一红08c为并行写卡会话在飞件)·独立验收审计通过·卡③v1.1停卡重裁实况见实现报告§5 2026-10-30 16:23:46 +08:00
DKChan 932edff505 收尾(工程批·工程小件批): 三条目完成整条删除(迭代42移交小件四条/恒真断言五条/闸基线三键换代——全部随 engsmall-20261030 批落地,合并提交 23bc6cc2)+批报告落档 reports/工程改动报告-工程小件批-2026-10-30.md;iter技能坑速查补条同批落盘(.zcode 不入 git) 2026-10-30 08:11:21 +08:00
DKChan 23bc6cc243 合回(工程批·工程小件批): 五件全清——run_all.gd --only 逗号解析修复(d32ff342)+level_runner 注释与 S8 消息订正(dd2de10e)+恒真断言五条判据修正(6a3203a8)+闸基线 02/05/09 三键换代(28223074,按回执实测绿重锚);批末闸两次全绿,最终回执 28223074 domain=code scope=related;孤立验证=逗号四词形态实跑四套件全绿(level_shop_economy 192 checks) 2026-10-30 08:08:40 +08:00
DKChan df6cafcb31 写卡(零豁免补件批·v1.1): T2机制按用户裁决改R2整族归件——实现段停卡呈报裁决落地:四_resume_*按所有权缝语句分家(件侧=主角账三件片段回填/curio池与特性还原/stop与bank片段回填/六成员改写;宿主侧=八项重绑定与两宿主调用按原语句序吸收进resume_adventure壳),快照两函数body逐字宿主输入逐调传参(件禁反向持宿主——RefCounted成环),纯读侧三函数逐字;宿主留_snapshot_breakpoint一行委托壳(CurioStop的Callable目标零改)与set_save_manager转发件;27键冻结与语句序零变化不变;裁决记录入§11 2026-10-30 08:08:07 +08:00
DKChan 2822307436 工程(工程小件批·件5): 闸基线 02/05/09 三键换代——按本批回执 6a3203a8 实测绿重锚(02 关联档分片4/4全绿/05 渲染闸真跑绿/09 静态扫描绿),三键旧红指纹(f9865f1a5a44/de89589e49ed/1a9ad773489f)全部归绿消除,改善态记账入 note;原登记=「02/09 两键」2026-10-30 并入 05 2026-10-30 08:01:50 +08:00
DKChan 6a3203a8db 工程(工程小件批·件4): 恒真与名实不符断言五条判据修正(存量断言精简A+B批余账④转正,按test-conventions §8.4真实入口口径改绑可观测面)——①feat_vouchers:51 恒真cond改绑投放装配面:读数==Σ表配额−本会话已兑付一张(curio_quota表驱动+兑付摘牌义务双绑);②treasure_effects:263 去恒真尾缀绑清图判定未触发(天眼窗口≠全板构造面稳定);③④level_shop_economy:594/:697 恒真min改绑胜局HP>0旗+扫描面非空(≥1胜局)双条件,HP≤0胜局即红;⑤system_contracts:66 标签互异半缺失补值集去重计数==原计数(同文件:44-55字典法同款);孤立验证=四套件实跑全绿(level_shop_economy 192 checks) 2026-10-30 07:54:40 +08:00
DKChan dd2de10ed1 工程(工程小件批·件2/3): 两处陈旧文本订正——level_runner.gd:12 node_entered 信号头注 shop={gp} 补 pool 态载荷形态(实际:507-509 两形态 legacy {gp}/pool {gp,shelf:true});static_scan.sh S8 ok 消息文件集 9→10(S8_FILES 实含 10 文件,board_installer.gd 随迭代42入集,扫面无缩小仅消息过期) 2026-10-30 07:54:18 +08:00
DKChan d32ff34256 工程(工程小件批·件1): run_all.gd --only 逗号形态解析修复——解析段补 split(",") 拆分对齐头注契约<词[,词…]>,此前整串当一个词对套件路径子串匹配,闸脚本关联档空格转逗号拼接单旗标传参多词必零命中 quit(2)(6caf56b5 起缺陷,迭代42施工曾以逐词重复传参绕过);孤立验证=逗号拼接四词形态实跑四套件全绿 filtered 2026-10-30 07:54:17 +08:00
DKChan 4e04d2f1ba iter44-卡3:卡③(测速循环批v1.1): 信号步进状态机消await-in-loop裸键·双实现对拍·行为改动零处·含并master与三卡产出物 2026-10-30 07:13:23 +08:00
DKChan d1c1d3822a iter44-卡2:卡②(战斗编排归约批): RoundCycle+BattleFacts两新件·宿主38→15·键账15/15/8·行为零变化·含消费漏网补改 2026-10-30 07:13:23 +08:00
DKChan d2b36e59cb iter44-卡1:卡①(组合根注册面批): ui_registry+三域容器建件·main.gd 49→15·全消费面改址main.ui.*·四闸主证行为零变化 2026-10-30 07:13:23 +08:00
210 changed files with 8896 additions and 7138 deletions

No files matched your search

+15 -7
View File
@@ -16,7 +16,7 @@
## 运行与验证(工作目录 = 含 project.godot 的切片目录) ## 运行与验证(工作目录 = 含 project.godot 的切片目录)
- **交付前验收 = 跑 `tools/run_all_gates.sh`,按本批改动域选档(`--code` 只跑代码域/`--docs` 只跑文档域/不带参数=两域全量),所选域全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,**域划分与选闸算法也只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单。域按**文件性质**判:代码域=`game/` 下非 `.md` 的行为载体(代码/测试脚本/探针/场景/数据表/素材),`game/` 内 `.md` 与 `game/` 外一切路径=文档域;同批两类都有时跑全量档。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件(关联档=只跑与本批改动关联的那部分测试),00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降,横切面改动(公共件/数据表/autoload/数值·存档·RNG·结算触面)回落全部套件;算法与套件族映射表只在闸脚本内定义。所选域全绿即可合回;全量回归由用户自发发起,合入后无自动兜底。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 与 `scope` 字段),合回时由 worktree-guard 按 diff 文件性质推出该验哪个域再校验回执(六况拦截细则见 process-conventions §3.9);非代码面的直改批验收=commit 点 git 钩子自动跑 `--docs`,无 worktree 与回执流程 - **合回验证 = 本地 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` - 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `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`(奖励期望);关卡完成率/难度类测量一律走全冒险 - 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`) - 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`)
@@ -40,7 +40,7 @@
| 时机 | 权威文档 | | 时机 | 权威文档 |
|---|---| |---|---|
| 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(代码面 worktree 批/其余主区直改)、五段流水线、批次清单、测试两入口与 MC/闸机细则、像素纪律 | | 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(一切改动走独立 worktree,主区只作合并落点)、合回后本地 CI、五段流水线、批次清单、测试两入口与 MC/闸机细则、像素纪律 |
| 流水线段入口 | 技能对照——doc-design=文档设计(兼任反哺点落文)/asset-ingest=美术录入/req-card=写卡/iter-plan=迭代编排/iter=实现(五者均装于项目 `.zcode/skills/`);登记不立技能;批后收尾与中间文档退役=batch-closure(同一目录);格式与状态机权威=`design/engineering/doc-conventions.md` §12 | | 流水线段入口 | 技能对照——doc-design=文档设计(兼任反哺点落文)/asset-ingest=美术录入/req-card=写卡/iter-plan=迭代编排/iter=实现(五者均装于项目 `.zcode/skills/`);登记不立技能;批后收尾与中间文档退役=batch-closure(同一目录);格式与状态机权威=`design/engineering/doc-conventions.md` §12 |
| 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) | | 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) |
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 | | 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 |
@@ -77,7 +77,15 @@
### 格式优先级 ### 格式优先级
枚举用列表,二维对照用表格,按内容形态选;散文是最后手段。 能画图的内容一律画图说明,图是第一优先载体;枚举用列表,二维对照用表格;散文是最后手段。
### 图优先
**判据**:描述含多个实体、实体间有明确关系(先后顺序、包含、依赖、状态转移、布局对比)的内容时,直接画图说明,不写长段文字;单点事实(一句装得下)不画,纯数据对照用表格。
**载体**:用 diagram-design 技能出 HTML/SVG 内联交付(流程/状态/结构/实体关系与空间对比、像素级示意、界面草图同一通道),大图精修与导出用 archify 技能;查看端不支持内联渲染时用字符画。
**边界**:硬数值与前提条件留在文字里,图不承载唯一数值依据;图与文禁重复说同一件事,文字只留图说不清的部分。
## 主动检索提醒 ## 主动检索提醒
@@ -85,12 +93,12 @@
## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约) ## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约)
- 车道铁律(判据与改动域划分同一,按文件性质切)——代码面(`game/` 下非 `.md` 的行为载体)唯一强制独立 worktree(迭代批全套仪式不变);其余一切面(含 `game/` 内 `.md` 与 `.zcode/`、`.qoder/` 等不入 git 面)主工作区直改+直提,commit 点由 git pre-commit 钩子(`tools/hooks/pre-commit`,`core.hooksPath` 挂载)现场跑文档域闸,绿落地红拦;主区直提代码面被该钩子拦;`--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 节) - **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节)
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测 - 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
- 交付前验收 = 跑 `tools/run_all_gates.sh` **按改动域**全绿(唯一入口、唯一闸清单、唯一域划分与选闸算法,禁在文档另列);代码域按**关联档选测**——02 单元与集成跑「卡片申报 ∪ diff 推导」并集套件、三件保险(冒烟/系统测试/静态扫描)恒跑、横切面回落全量,所选域绿即可合回,全量回归由用户自发(2026-10-29 用户裁,合入后无自动兜底)(算法见闸脚本);合回 master 由 worktree-guard 按 diff 文件性质推域校验回执强制(process-conventions §3);非代码面直改批的验收=commit 点 git 钩子自动跑 `--docs`(process-conventions §1) - 合回后验证 = 本地 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/`;批末只跑代码域闸。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,登记在案后由用户发令在主工作区直改+直提(commit 点 git 钩子自动跑文档域闸),完成即删条目;触及代码面的工程改动(如纯测试维护)仍走 worktree 批;文档设计类写入走 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)。工程/文档设计/美术素材三类改动不走写卡与编排段:登记 →(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸 → 完成删条目(车道细则=process-conventions §1) - 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <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 采集档位表」,一档只能由用户发起 - MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6) - 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
- **验收闭合**——每个需求点与每个行为改动都要有从真实入口进入的验收断言,可 grep 定位、在当轮真实跑批中执行并通过,判据绑被测改动特有的可观测提交面;**禁变异类测试与撤销类取证**(2026-10-24/2026-10-28 用户裁——人为制造红属伪造证据),证明做不出=停卡呈报;禁删断言/放宽阈值换绿;合回前独立验收审计未过则禁止合并(细则=test-conventions §8+iter 技能第 5 步) - **验收闭合**——每个需求点与每个行为改动都要有从真实入口进入的验收断言,可 grep 定位、在当轮真实跑批中执行并通过,判据绑被测改动特有的可观测提交面;**禁变异类测试与撤销类取证**(2026-10-24/2026-10-28 用户裁——人为制造红属伪造证据),证明做不出=停卡呈报;禁删断言/放宽阈值换绿;合回前独立验收审计未过则禁止合并(细则=test-conventions §8+iter 技能第 5 步)
+16 -11
View File
@@ -17,7 +17,7 @@
**通用规格纪律**: **通用规格纪律**:
- 交付格式 PNG 透明底;动画用序列帧或精灵表(SpriteFrames),帧数规格随样张定(候选档已成文 art-bible §8.4)。命名规范候选草案见 art-bible §8.3。 - 交付格式 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 不明的一律不用。 - **复用闸(♻ 渠道四道验收,一道不过不入包)**:① 色板量化——改色对齐 art-bible §4.1 token,不保留原包杂色;② 吸色检查——无 HSV 色相 300°–345°(紫赤禁入带);③ 灰度/色弱可辨——去色相后仍可辨且不与语义件混淆;④ 授权干净——CC0 优先,CC-BY 署名记入设置/关于页,license 不明的一律不用。
@@ -59,14 +59,14 @@
## 2. P1 —— 角色级(样张通过后开工,ComfyUI) ## 2. P1 —— 角色级(样张通过后开工,ComfyUI)
> 提示词规格卡 art-prompts.md 现行为 **v2.0**(主角三态+八敌种,逐资产自包含完整提示词,整段可直贴)。 > 提示词规格卡 art-prompts.md 现行为 **v2.13**(主角三态+八敌种逐卡提示词仍在册;站桩立绘走 §5.4 按档派生、不重画)。
### 2.1 主角战斗小人 ### 2.1 主角战斗小人
- **要什么**:待机 / 攻击 / 受击 三态起步(移动态样张后定);透明底 PNG。 - **要什么**:待机 / 攻击 / 受击 三态起步(移动态样张后定);透明底 PNG。
- **风格/规格**:小人原型——**窄、直立、对称,轮廓凹凸 ≤2**;2~3 头身两档下剪影均成立;主色小人蓝 `#2E86DE`(候选值,样张冻结终稿);格内占位最窄、不吞数字位。 - **风格/规格**:小人原型——**窄、直立、对称,轮廓凹凸 ≤2**;2~3 头身两档下剪影均成立;主色小人蓝 `#2E86DE`(候选值,样张冻结终稿);格内占位最窄、不吞数字位。
- **来源**:✦ ComfyUI(prompt 卡 v2.0 §2);剪影原型是设计载荷(宽≥高/凹凸计数/甲档/紫赤独占),素材站无对的,不复用。 - **来源**:✦ ComfyUI(prompt 卡 v2.0 §2);剪影原型是设计载荷(宽≥高/凹凸计数/甲档/紫赤独占),素材站无对的,不复用。
- **现状**:站姿样张已有先例(源文件已清理);战斗三态未出。 - **现状**:战斗三态在役(迭代 6 T3 落位);2026-11-01 显示档改中档 144,按档派生落位归立绘显示档实装批。
### 2.2 八敌种形象(键集事实源 = `data/tables/enemy.tres` 行键) ### 2.2 八敌种形象(键集事实源 = `data/tables/enemy.tres` 行键)
@@ -81,7 +81,7 @@
| 钩爪恐魔 hook_horror | 普通怪(钩爪/多足轮廓注意凹凸计数控制) | light | 第三关群行捕食者,小体量可辨 | | 钩爪恐魔 hook_horror | 普通怪(钩爪/多足轮廓注意凹凸计数控制) | light | 第三关群行捕食者,小体量可辨 |
| 灰矮人石君王 duergar_king | **Boss:锐角与不对称**,体量最大 | heavy | 第三关 Boss;紫赤色相独占+形状独占双通道 | | 灰矮人石君王 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——不复用。 - **来源**:✦ ComfyUI(prompt 卡 v2.0 §3,八敌种+L2 熊地精变体共 9 卡);理由同 §2.1——不复用。
- **联动**:显示名以 `data/tables/enemy.tres` 与敌人系统文档为准(L2 熊地精=bugbear 复用,显示名熊地精系)。 - **联动**:显示名以 `data/tables/enemy.tres` 与敌人系统文档为准(L2 熊地精=bugbear 复用,显示名熊地精系)。
@@ -130,10 +130,10 @@
### 3.5 营地场景图 ### 3.5 营地场景图
- **要什么**:营地场景图 9 张——`scene_camp_bg`(背景)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件)。钥石顶栏图标沿用现役 `icon_keystone`。 - **要什么**:营地场景图 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) 头注轻量口径执行:用途+生成时间必注,其余参数有则记。** - **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#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 像素字,已定稿单套) ### 3.6 字体(全局 UI 像素字,已定稿单套)
@@ -155,13 +155,18 @@
| 资产 | 需求 | 风格/规格要点 | 来源 | | 资产 | 需求 | 风格/规格要点 | 来源 |
|---|---|---|---| |---|---|---|---|
| 受击反馈 / 死亡表现 / 顺位标记 | 敌人系统登记的资产需求**下限三类** | 死亡=血条归零后**灰化+碎裂退场,禁止瞬间消失**;顺位标记需在敌多时可读 | ⚙/✦ 挂自产精灵——死亡灰化碎裂是语义硬规则,不可整包复用;烟尘碎屑粒子可 ♻ 改色 | | 受击反馈 / 死亡表现 / 顺位标记 | 敌人系统登记的资产需求**下限三类** | 死亡=血条归零后**灰化+碎裂退场,禁止瞬间消失**;顺位标记需在敌多时可读 | ⚙/✦ 挂自产精灵——死亡灰化碎裂是语义硬规则,不可整包复用;烟尘碎屑粒子可 ♻ 改色 |
| B 区舞台背景插画 `scene_stage_bg.png` | 战斗舞台背景(art-bible §6.1 资产契约) | **✦ 插画级**;源 1280×270、B 区全高含明细带底、显示 1×;**只出远景 + 中景两带**(近景带在结算期被 84px 明细带底整条盖住);三关换挡走 §4.3 `wall_ambient` tint 槽,不按板出三张 | ✦ 插画级(作者自跑远程生图管线) | | B 区舞台背景层 `scene_stage_bg_dungeon.png` | 战斗舞台背景·dungeon 变体(art-bible §6.1 资产契约) | **✦ 插画级**;源 = 基准屏宽 × `stage_h`(读布局表 `battle_layout.tres`)、B 区全高、显示 1×;顶部空区+纵深洞厅远景(远/中/近三层;暖石棕、宽岩台/层理、地精巢穴支洞/木栈道/木桩围栏),不画近景地面;按 `board_type` 选图,不走 `wall_ambient` tint | ✦ 插画级(作者自跑远程生图管线) |
| 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint | ⚙/✦ 自产 | | B 区舞台背景层 `scene_stage_bg_underdark.png` | 战斗舞台背景·underdark 变体(art-bible §6.1 资产契约) | **✦ 插画级**;源 = 基准屏宽 × `stage_h`、B 区全高、显示 1×;顶部空区+纵深洞厅远景(远/中/近三层;冷蓝灰、巨柱/石笋/蛛网/螺旋塔剪影/巨菇/石桥),不画近景地面;按 `board_type` 选图,不走 `wall_ambient` tint | ✦ 插画级(作者自跑远程生图管线) |
| B 区舞台背景层 `scene_stage_bg_deep_underdark.png` | 战斗舞台背景·deep_underdark 变体(art-bible §6.1 资产契约) | **✦ 插画级**;源 = 基准屏宽 × `stage_h`、B 区全高、显示 1×;顶部空区+纵深洞厅远景(远/中/近三层;灰黑、石笋城郭/棱角石厅/石桥/矿道口/烟囱塔、冷银/铜灰矿脉),不画近景地面;按 `board_type` 选图,不走 `wall_ambient` tint | ✦ 插画级(作者自跑远程生图管线) |
| B 区连续近景地面层 `scene_stage_ground.png` | 近景泥土地面层(art-bible §6.1 图层归属) | **✦ 插画级**;源 = 基准屏宽 × `ground_h`(布局表新增字段);干净压实泥土为主、全幅只零星几颗小石、散点分布,顶缘不规则泥土边/阴影缝作分隔带;三关共用、跨三关恒定、不吃 `wall_ambient`;独立层压在 B 区底部,小石台在其上 | ✦ 插画级(作者自跑远程生图管线) |
| 站桩石板地台件 `scene_stage_plaza.png` | 近景地台独立件(art-bible §6.1/§8.2) | 源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定,不吃 `wall_ambient` tint;连续地面层在其下方,先保留 | ⚙/✦ 自产 |
| 特殊技动画终稿 | 并入 §2.3 三档意图动画,不另起体系 | 攻/防两型旧口径由三档替代 | ✦/⚙ 自产(同 §2.3) | | 特殊技动画终稿 | 并入 §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);小石台件已在役。
--- ---
## 5. 明确不在本清单 ## 5. 明确不在本清单
@@ -182,7 +187,7 @@
└─> 敌种呈现形态裁决 ✅已完成(全站桩,卡片式已弃) └─> 敌种呈现形态裁决 ✅已完成(全站桩,卡片式已弃)
徽章(3.1)/节点图(3.2)/效果图标(3.4) —— 独立线,可随时插队 徽章(3.1)/节点图(3.2)/效果图标(3.4) —— 独立线,可随时插队
♻ 复用件(3.2/3.4/3.5/3.6/演出粒子) —— 不依赖样张,可随时插队(入包闸 = §0 复用四道) ♻ 复用件(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)——像素基准/画布/命名/帧数候选值就位,终值归样张批冻结 分辨率档位 ✅已定案(1280×800 + 1920×1080,2026-09-22 用户确认);art-bible §8 资产规范 ✅已成文(2026-09-23)——像素基准/画布/命名/帧数候选值就位,终值归样张批冻结
``` ```
+32 -27
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper # 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.23,历次修订明细见 git 提交史 > 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.35,历次修订明细见 git 提交史
> >
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。 > **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。 > 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -30,7 +30,7 @@
**原则二|明亮为皮,明度分档为骨**(取自 Moonlighter、Peglin;锚定 P1 可读性前提、P4 探险氛围) **原则二|明亮为皮,明度分档为骨**(取自 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 沸腾。 > 本节服从第 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〉 > **世界底层两子带**:世界底层拆两条子带——**底层下沿带 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.2 轮末自动战斗结算
- **主情绪**:延迟兑现的清算爽感——扫雷之因逐行兑现为战果;完美封印(概念定义与兑付结构见《地图战斗资源》)的高光兑现——结算带高光行,以其为全场喝彩级的最高反馈档 - **主情绪**:延迟兑现的清算爽感——扫雷之因逐行兑现为战果;完美封印(概念定义与兑付结构见《地图战斗资源》)的高光兑现——结算带高光行,以其为全场喝彩级的最高反馈档
- **色彩性格**:情绪不走明度(§2 禁令),色彩转向金橙主导、饱和拉高;数字色转译为弹道色沿行推进 - **色彩性格**:情绪由色相与饱和承载——金橙主导、饱和拉高,明度分层不动;数字色转译为弹道色沿行推进
- **氛围形容词**:酣畅、递进、一气呵成 - **氛围形容词**:酣畅、递进、一气呵成
- **能量**:7/10,随连击爬升 - **能量**:7/10,随连击爬升
- **承载元素**:兑现光带逐行横扫棋盘,开格化箭雨、印记化暴击砸向敌人,因果全程可见可归因。验收:目检〈兑现光带逐行横扫、因果可见可归因;情绪表达不走压暗〉 - **承载元素**:兑现光带逐行横扫棋盘,开格化箭雨、印记化暴击砸向敌人,因果全程可见可归因。验收:目检〈兑现光带逐行横扫、因果可见可归因〉
### 2.3 清图奖励与换图 ### 2.3 清图奖励与换图
- **主情绪**:短促丰收——宝箱揭盖式的喜悦,三五秒即收,不拖节奏 - **主情绪**:短促丰收——宝箱揭盖式的喜悦,三五秒即收,不拖节奏
- **色彩性格**:全屏饱和峰值:金粒喷泉与彩纸迸发,饱和瞬时拉满后回落;明度不动(§2 情绪禁令) - **色彩性格**:全屏饱和峰值:金粒喷泉与彩纸迸发,饱和瞬时拉满后回落;明度分层不动
- **氛围形容词**:丰收、轻快、干脆 - **氛围形容词**:丰收、轻快、干脆
- **能量**:8/10,尖峰后速降 - **能量**:8/10,尖峰后速降
- **承载元素**:最后一格碎砖中喷出的 GP 喷泉,叮当落入 UI 钱袋。验收:目检〈金粒喷泉短促收敛,饱和峰值后回落〉 - **承载元素**:最后一格碎砖中喷出的 GP 喷泉,叮当落入 UI 钱袋。验收:目检〈金粒喷泉短促收敛,饱和峰值后回落〉
@@ -108,7 +108,7 @@
### 2.5 战斗失败(冒险终结) ### 2.5 战斗失败(冒险终结)
- **主情绪**:温柔的惋惜与「再来一次」的邀请——有分量、不惩罚,像合上一册冒险日志 - **主情绪**:温柔的惋惜与「再来一次」的邀请——有分量、不惩罚,像合上一册冒险日志
- **色彩性格**:情绪不走明度(§2 禁令),色相转暖灰掺暮蓝;全作禁血红与黑场,唯此一拍最静 - **色彩性格**:情绪由色相承载——色相转暖灰掺暮蓝,明度分层不动;全作禁血红与黑场,唯此一拍最静
- **氛围形容词**:惋惜、安静、余温、可再来 - **氛围形容词**:惋惜、安静、余温、可再来
- **能量**:1/10 - **能量**:1/10
- **承载元素**:小人坐在碎砖上摇头掸灰,落下的封印印记逐枚化作光点收回行囊。验收:目检〈无血红与黑场;色相转暖灰掺暮蓝〉 - **承载元素**:小人坐在碎砖上摇头掸灰,落下的封印印记逐枚化作光点收回行囊。验收:目检〈无血红与黑场;色相转暖灰掺暮蓝〉
@@ -159,7 +159,7 @@
### 3.2 环境几何 ### 3.2 环境几何
- **雷图棋盘呈现基准**:战棋地块质感 × 正俯视角——取 ITB 参考图(`references/itb_1.jpg`)的地表质感骨架:地块纹理多样、起伏体块感、岛缘/砖缘厚度、格上装饰小立牌;但相机一律正上方垂直向下,格线屏轴对齐,**禁等距/斜视角与菱形格**;形状语言仍守总纲圆角方形。砖缘厚度与岛缘立体面属形体塑造,不属状态/情绪表达,不受 §2 情绪明暗禁令约束(属 §2 明度分层允许域;顶面格底仍守 §4.1 参数域)。装饰小立牌属 ITB 参考的观感元素,本作不采用;装饰变体走氛围层纪律(纹理级/物件级形状家族,见下方装饰变体条),零信息纪律以原则三为准。验收:目检〈棋盘正俯视、格线屏轴对齐、无菱形格〉 ←原二;原三;§2 - **雷图棋盘呈现基准**:战棋地块质感 × 正俯视角——取 ITB 参考图(`references/itb_1.jpg`)的地表质感骨架:地块纹理多样、起伏体块感、岛缘/砖缘厚度、格上装饰小立牌;但相机一律正上方垂直向下,格线屏轴对齐,**禁等距/斜视角与菱形格**;形状语言仍守总纲圆角方形。砖缘厚度与岛缘立体面属形体塑造(顶面格底仍守 §4.1 参数域)。装饰小立牌属 ITB 参考的观感元素,本作不采用;装饰变体走氛围层纪律(纹理级/物件级形状家族,见下方装饰变体条),零信息纪律以原则三为准。验收:目检〈棋盘正俯视、格线屏轴对齐、无菱形格〉 ←原二;原三;§2
- **地砖**:一切环境体由同一圆角方形衍生:未开 = 完整凸砖,已开 = 同形缺角碎裂,雷 = 碎裂加巢穴纹(危险格玩家说法见《排雷玩法》);只以"完整度"叙事,不为状态新造形。验收:目检〈三格态仅以完整度区分,无新造状态形〉 ←原三;P4 - **地砖**:一切环境体由同一圆角方形衍生:未开 = 完整凸砖,已开 = 同形缺角碎裂,雷 = 碎裂加巢穴纹(危险格玩家说法见《排雷玩法》);只以"完整度"叙事,不为状态新造形。验收:目检〈三格态仅以完整度区分,无新造状态形〉 ←原三;P4
- **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三 - **装饰变体(未开格氛围层)**:覆尘完整砖之上叠加裂纹/碎石斑/物件级点缀等装饰。物件级装饰(小石块/陶片/骨殖等)须走**专属装饰形状家族**(与信息元素形状语言隔离、灰度下不与任何逻辑元素同形),数量配额按图类型定(候选 3–5 种,样张已冻结,§1.4),一律残缺、内凹、低对比而自然后退(§3.4);零信息纪律(分布随机、与雷/安/类型零相关、灰度下禁止制造新分类特征、分布每板一次跨轮不复掷)以原则三为准,此处不赘。验收:目检〈物件级装饰不与任何逻辑元素同形;装饰残缺低对比后退〉 ←原三
- **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3 - **结果整格表达(已开格)**:已开格按当格结果整格表达(结果清单见原则三);画法——产资源格的家族物件纹样用 §3.1 道具点剪影语言(直线几何体)角嵌于砖缘,砖心与数字字形占位区留白(已开格仍承载数字);危险格巢穴纹较已开格更凶一档,形状+颜色双通道。验收:目检〈产资源格嵌家族纹样且砖心留白;巢穴纹更凶一档〉 ←原三;P3
@@ -172,7 +172,7 @@
- **高墙瓦片体系|光照纪律(单一北受光)**:北缘 = 墨线内侧 1px 微亮棱(就地取色提亮,禁宽亮带);西/东缘 = 墨线后直进纹理、不加修饰;南缘 = 墨线 + 立面窄暗条(约 1/3 格高,横层理叠石、禁竖条纹栅栏感)。验收:目检〈北缘亮棱、南缘窄暗条;无宽亮带、无竖条纹栅栏感〉 - **高墙瓦片体系|光照纪律(单一北受光)**:北缘 = 墨线内侧 1px 微亮棱(就地取色提亮,禁宽亮带);西/东缘 = 墨线后直进纹理、不加修饰;南缘 = 墨线 + 立面窄暗条(约 1/3 格高,横层理叠石、禁竖条纹栅栏感)。验收:目检〈北缘亮棱、南缘窄暗条;无宽亮带、无竖条纹栅栏感〉
- **高墙瓦片体系|凹角收头**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出。验收:目检〈凹角收头与转角竖线同列,无悬空突出〉 - **高墙瓦片体系|凹角收头**:角点单像素落线;南立面侧凹角加 1px 立面收头——带身与转角竖线同列收尾,禁悬空、禁突出。验收:目检〈凹角收头与转角竖线同列,无悬空突出〉
- **高墙瓦片体系|现行定稿**:`game/assets/art/board/terrain_*`(16 唯一墙瓦片 + 5 地面瓦片变体;词表与查表逻辑落码于 `board_world.gd`)。验收:机检〈terrain_* 素材 16 墙瓦片 + 5 地面变体在库,查表逻辑与词表一致〉 - **高墙瓦片体系|现行定稿**:`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,让位于数字/小人/旗);原三 - **变异格形状通道**(双通道之"形",与色无关):底砖圆角外轮廓不变,砖面内缘绘一圈定宽专属几何纹带(纹带形全作不与他类共用),砖心留白予数字与旗;验收标准 = **灰度截图下仍可分类**。种类清单 = 地形变异六类型已定稿(地形变异 GDD;荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘):**纹带形按轴只分两种**——伤害轴(荆棘丛/岩浆/落石)= 刺/缺排斥形,信息轴(藤蔓缠绕/烬覆/矿尘)= 遮蔽/缠绕形;同轴跨图类型只做色板变体(§4.3 `variant_tint` 槽),不做新纹形。岩浆纹带橙红须与危险红互辨(变异格不是雷,色相邻近有混淆风险);信息轴遮蔽呈现禁止侵入数字字形占位区(§1.4 验收 1 延伸)。验收:目检〈六类型纹带灰度下仍可分类,同轴不新增纹形;岩浆纹带与危险红互辨〉 ←原一(纹理位次第 4,让位于数字/小人/旗);原三
### 3.3 UI 形状语法(方向指导,细则见 §7.1) ### 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〉 - **图标槽柔和档 `#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 档〉 - **群伤微光暗档 `#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 且本值在册〉 - **站桩石板地台板身 `#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〉 - **重甲冷灰金属材质色 `#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〉 - **卓尔浅紫灰皮 `#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 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉 - **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#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 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉
- **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:`#3E342A`(舞台底,代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底);`#C4AC84`(地台顶缘受光线,代码 `STAND_STONE_RIM`)→ 并入 `#C9AF83`(掩尘,并与 §8.2 高墙口沿高光 `Color8(196,172,132)` 统一为同一枚);`#5A4C3A`(地台描边,代码 `STAND_STONE_EDGE`)→ 并入 `#4C4038`。验收:机检〈全仓三枚源常量零引用,绘制点全部改指合并目标色〉 - **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①地台顶缘受光线 `#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 **对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **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
@@ -376,26 +377,31 @@
### 6.1 战斗舞台背景(B 区环境) ### 6.1 战斗舞台背景(B 区环境)
- **双舞台常驻下的 B 区环境**:战斗画面区(站桩立绘所在)的背景 = 明亮地窟,与 §1 风格基调同源——暖倾向、无压抑;背景整体**低对比自然后退**(§3.4 焦点预算延伸):背景禁止构成完整凸形、禁止抢 B 区立绘与 D 区数字的焦点,识别层级恒为 立绘 > 背景。验收:目检〈背景无完整凸形、立绘最抢眼〉 - **双舞台常驻下的 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〉 - **舞台正面目标**: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 同款逻辑)。验收:方向性
- **图类型联动**:背景 tint 走 §4.3 `wall_ambient` 槽,随三关换挡(方向候选见 §6.3);紫赤仅 Boss 战允许短暂进入背景氛围纹样(§2.6 色相独占的舞台侧延伸,高饱和独占不变),非 Boss 战高饱和紫赤禁入。验收:机检〈背景吸色无非 Boss 高饱和紫赤〉 - **图类型联动**:背景层按 `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` 槽〉
- **资产渠道**:✦ 插画级(演出类场景贴图,清单 §4 联动登记);战斗一屏样张(§1.4 清单 1.2)含背景出样。**资产契约(尺寸候选,样张冻结终稿)**:基准一张 = `scene_stage_bg.png`(源 1280×270,B 区全高含明细带底;三关换挡走 §4.3 `wall_ambient` tint 槽,不按板出三张——换皮成本复用棋盘同款逻辑),显示 1×;呈现约束按本节各条(低对比后退/明度世界底层档/光柱氛围保留)。程序化平色+光柱现行使用 = 占位;插画工单归 PRD,交付后 art-status-checklist 同步 ✅。**出图只出远景 + 中景两带**:结算期明细带底 84px 会盖住 B 区 78%–100% 段(约 59px),近景带在结算期整条不可见——画进背景等于画一件永远看不见的东西。远景/中景的构图占比归整屏样张(§1.4)。验收:目检〈背景插画内零地台结构件〉 - **场景构成(2026-10-31 用户裁)**:B 区舞台从远到近分三段——**顶部空区**:洞顶以上的昏暗空间,只留深色空区,**不画钟乳石、吊锥、悬挂物或任何可辨形状**;**远景(纵深洞厅)**:不是贴墙的后壁,按远/中/近三层组织——远层暗剪影(远处洞口/通道、层叠岩体、洞底轮廓)、中层主要细节层(岩台、岩柱、凹壁、裂缝)、近层左右暗色岩框;剪影被中层部分遮挡、不贴顶、不孤立,**禁贴墙感、禁单件圆柱贴顶、禁砖石墙/规则砌缝/方整石块**;三关母题(地精=支洞/木栈道/木桩围栏;卓尔=巨柱/石笋/蛛网/螺旋塔剪影/巨菇/石桥;灰矮人=石笋城郭/棱角石厅/石桥/矿道口/烟囱塔)见 `battle-stage-prompts.md` §3.2;**分隔带**:远壁根部与近景地面之间一条不规则碎石带/阴影缝,边界清楚但不笔直,**禁瓷砖缝、踢脚线、规则台阶**;**近景地面**:干净压实泥土为主,全幅只零星几颗小石、散点分布,中央平缓留空给角色站位,**禁成排/成堆碎石、满铺砂砾、泥点噪声、瓷砖、规则铺装、草、木地板**。纵深与三段构图归 §1.4 样张;逐条要素、分层提示词与否定后缀 = `battle-stage-prompts.md`(本文件不重复维护)。验收:目检〈顶部无钟乳石/吊锥;远景有纵深、非贴墙;无孤立圆柱贴顶;近景为泥土碎石;分隔带不规则;中央无大障碍〉
- **近景地台走独立件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形(规格见 §8.2)。独立的两条理由——① 上条的 84px > 59px 覆盖账;② §4.3 `wall_ambient` tint 作用于整张背景,地台若画进背景会随三关换明度,而 §6.1 舞台正面目标把地台定死为世界中层承载面,随关卡变会破 §2 明度分层的层间关系。独立件跨关恒定。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉 - **图层归属(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` 槽〉
- **资产渠道与契约**:✦ 插画级(演出类场景贴图,清单 §4 联动登记);战斗一屏样张(§1.4 清单 1.2)含背景出样。**背景层 = 三张按关卡变体** `scene_stage_bg_dungeon.png` / `scene_stage_bg_underdark.png` / `scene_stage_bg_deep_underdark.png`(源 = 基准屏宽 × `stage_h`,读布局表 `game/data/tables/battle_layout.tres`;显示 1×;按 `board_type` 选图;**不走 `wall_ambient` tint**);三张共用本节构图规则,仅岩体形态与色族按 §6.3 三关方向不同。**背景只画顶部空区+纵深洞厅远景,不画近景地面**:结算期明细带整条压住 B 区底带(带高 = `log_band_h_settle`,读布局表),近景地面在独立层;画进背景等于画一件永远看不见、且会随背景变体重复的东西。**连续地面层** = `scene_stage_ground.png`(源 = 基准屏宽 × `ground_h`,读布局表;显示 1×;三关共用、跨关恒定、不吃 tint);地面层是极宽薄条,出图与后处理按 `battle-stage-prompts.md` §2.2 的工艺(可平铺纹理条/最宽档裁切,禁横向拉伸)。**光柱不迁入背景**:三张背景均不画光柱/光带/光束/光晕与任何光源实体,明暗只由岩体层理与暗部承载;现行四柱程序层是临时占位,资产到位后直接退役(代码批)。资产已入包(2026-10-31),四柱程序层与平色底由代码批退役/改指;出图工单 = `battle-stage-prompts.md`,交付记录见本节「B 区舞台背景与共用地面的交付绑定」。验收:目检〈三关背景可辨为不同洞窟,非同一图换色;远景有纵深、非贴墙;无孤立圆柱贴顶;背景无地台/地面结构件、无砖石墙、无瓷砖地面、无光柱/光带;地面层为泥土/砂砾/碎石〉
- **B 区舞台背景与共用地面的交付绑定(2026-10-31 入包)**:三张背景与一张地面已按本节契约入包 `game/assets/art/scene/`——`scene_stage_bg_dungeon.png` / `scene_stage_bg_underdark.png` / `scene_stage_bg_deep_underdark.png` 均 1280×252、不透明全幅、单张 ≤32 色、按 `board_type` 选图;`scene_stage_ground.png` 1280×100、顶缘以上透明、单张 ≤32 色、三关共用。地面按作者 2026-10-31 裁保留候选 v12b 原亮度(实测不透明域 L≈0.132,低于 §2 世界中层 0.22–0.45,登记为作者例外;后续调亮归调参/替换,不重开设计);背景实测 L:dungeon 0.0148、underdark 0.0132、deep_underdark 0.0111(后两者低于世界底层下沿带下限 0.014 不超过 0.003,登记读数)。验收:机检〈四件文件名与尺寸逐位相符;地面顶缘以上 alpha=0、不透明域 ≤32 色;背景不透明全幅 ≤32 色〉+目检〈三关可辨、无光柱、地面读作泥土〉。参考指针:`asset-path-map.md` `scene/` 行、`battle-stage-prompts.md` §2/§4。
- **近景地面与地台走独立件**:连续地面层 `scene_stage_ground.png` 与角色脚下小石台 `scene_stage_plaza.png` 均为独立层,三关共用、跨关恒定。小石台源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形(规格见 §8.2);连续地面层的顶缘即分隔带(不规则碎石/阴影缝,不是直线亮边)。独立的两条理由——① 三张背景按关卡变体,地面若画进任一背景就得三张各画一遍、且三关站位面会漂移;② 结算期明细带整条压住底带(带高读 `log_band_h_settle`),地面在独立层才能跨关恒定并保持世界中层承载面不变。小石台先保留,是否退役归实装批/后续裁决。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉+目检〈连续地面为泥土碎石、顶缘不规则〉
### 6.2 营地场景 ### 6.2 营地场景
- **情绪与色域**:承载 §2.8——全作最暖:烛光黄 `#F6C667` × 木色 `#8A5A34`(§7.2),中等饱和,无棋盘式高对比。 - **情绪与色域**:承载 §2.8——全作最暖:烛光黄 `#F6C667` × 木色 `#8A5A34`(§7.2),中等饱和,无棋盘式高对比。
- **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=木围墙(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**(门洞内一条通向远处的小路);近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙(围墙是纯背景板),唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。 - **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=**独立成层的木围墙**(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**;近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙,唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。
- **交付形态**:**背景 1 层 + 七个物件各 1 层**(**帐篷**为承载件、不可点),共 8 张透明 PNG + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。 - **图层拆分(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 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 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 两道门)。 - **色彩与氛围依据**:营地场景的冷暖分工(冷的大面积后退、暖的小面积前进)、空气透视三档与大面积用色规则 = `reports/提案-营地色彩与氛围脚本-2026-10-23.md`。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。 - **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。
- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。 - **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。
- **事实源边界**:解锁条件/价格/存档写入归元进度 GDD 与 `MetaProgression`;素材命名与路径归 `asset-path-map`;清单登记归 `art-assets-checklist` §3.5。 - **事实源边界**:解锁条件/价格/存档写入归元进度 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 换挡方向) ### 6.3 三关图类型色板候选(board_type 换挡方向)
- **模板与不变槽**:换皮只换 §4.3 四槽(`tile_closed`/`tile_open`/`wall_ambient`/`variant_tint`),信息与语义层全图类型恒定——本节只给三关**方向性候选**,终值按 §4.3 现行规则重跑 §1.4 格子样张冻结。**终值的取值程序**:先跑「三关色相 × 五组类型微光」互辨抽测(抽测任务登记在待办清单),再按 §4.3 现行规则重跑 §1.4 格子样张冻结——本节不填终值色值,待该抽测与格子样张重跑后落本节修订。验收:机检〈本节三关行无 `tile_closed`/`wall_ambient` 终值色值字面量(终值未冻结前禁填)〉 - **模板与不变槽**:换皮只换 §4.3 四槽(`tile_closed`/`tile_open`/`wall_ambient`/`variant_tint`),信息与语义层全图类型恒定——本节只给三关**方向性候选**,终值按 §4.3 现行规则重跑 §1.4 格子样张冻结。**终值的取值程序**:先跑「三关色相 × 五组类型微光」互辨抽测(抽测任务登记在待办清单),再按 §4.3 现行规则重跑 §1.4 格子样张冻结——本节不填终值色值,待该抽测与格子样张重跑后落本节修订。验收:机检〈本节三关行无 `tile_closed`/`wall_ambient` 终值色值字面量(终值未冻结前禁填)〉
- **三关背景变体**:§6.1 的三张背景取本节三关方向作为岩体形态、剪影语言与色族差异(dungeon 暖石棕+地精巢穴支洞/木栈道/木桩围栏;underdark 冷蓝灰+卓尔巨柱/石笋/蛛网/螺旋塔剪影/巨菇/石桥;deep_underdark 灰黑+灰矮人石笋城郭/棱角石厅/矿道口/烟囱塔);背景不复用 `wall_ambient` tint(该槽作用于棋盘/世界),背景色值归战斗屏场景环境子板。验收:目检〈三关背景可辨为不同洞窟,非同一图换色〉
- **深度轴叙事**(§2.7 下探过渡):色相层进预告「更深更险」,明度档不动—— - **深度轴叙事**(§2.7 下探过渡):色相层进预告「更深更险」,明度档不动——
- **dungeon 地精洞窟**(第一关):现行掩尘系暖石棕(`#C9AF83` 基)即基准档,`wall_ambient` 暖棕; - **dungeon 地精洞窟**(第一关):现行掩尘系暖石棕(`#C9AF83` 基)即基准档,`wall_ambient` 暖棕;
- **underdark 幽暗地域**(第二关;方向:**冷调幽暗**):主调**灰白蓝**候选(低饱和蓝灰域,`tile_closed`),少量紫黑倾向点缀——紫系取紫罗兰域(≈256°)候选,禁入带 300°–345° 不变;`wall_ambient` 冷蓝灰; - **underdark 幽暗地域**(第二关;方向:**冷调幽暗**):主调**灰白蓝**候选(低饱和蓝灰域,`tile_closed`),少量紫黑倾向点缀——紫系取紫罗兰域(≈256°)候选,禁入带 300°–345° 不变;`wall_ambient` 冷蓝灰;
@@ -417,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 - **记忆态可读性判据**:数字对合成底**归一后(真实渲染面)三关 ≥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 - **迷雾过渡边:硬边(逐格二值)**:灯光区边界 = **整格二值切换**(区内全渲染 / 区外全黑或记忆态),**不做渐变过渡带、不做半透明晕边**。理由:①边界是信息边界不是氛围晕——渐变中间态 =「看得见但看不清」,破数字可读硬指标与记忆态明度档;②现行事实源验收断言(边界格呈态、两态逐位一致)须格级二值可判;③与砖面整格表达(§3.2 高墙瓦片体系)、ITB 格内信息分层气质同轴。无动画,减动效无涉。←现行事实源;§1.2 原则一;§3.2
- **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉 - **与结算期去饱和的叠加共存**:结算期去饱和偏移(settlement_fx_view,同族先例)与迷雾调制叠加规则 = **记忆态为终态档**——区外已开格在结算期保持记忆态档、**不再叠加**结算去饱和(灰显已含去饱和语义,二次调制无信息增量且明度不可控);灯光区内格照常吃结算去饱和;全黑格两态无差。叠加态随实施批共存校验 + 探针断言(provisional,实机目检可代拍)。验收:机检〈叠加态下记忆态灰显不被二次调制〉
- **§2 明度禁令自查**:迷雾遮蔽与记忆态灰显属 §2 明度分层**光照氛围允许域**(洞穴举灯的空间光照呈现,与 Moonlighter 取列「亮地板/暗深渊/发光体」光照层次同族),非情绪压暗(§9.2 Darkest 反面条款不触——那禁的是情绪表达)、非状态明暗编码(格态语义仍由形+色相承载,灰度下三态可辨不受扰)。←§2;§9.1;§9.2
--- ---
@@ -447,7 +452,7 @@
### 7.3 布局栅格 ### 7.3 布局栅格
- **结构**:双舞台三区(B 战斗画面/C 数据框/D 棋盘;字母保持不重编)为结构冻结项,分区尺寸与像素级裁决归 battle.md(HUD框架承载 1280×800 基座锚点值,如扫雷图区 1036×504、结算明细带展开 84px)。验收:机检〈三区结构常驻〉 - **结构**:双舞台三区(B 战斗画面/C 数据框/D 棋盘;字母保持不重编)为结构冻结项,分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`,分区尺寸与像素级裁决归 battle.md(HUD框架只载 1280×800 基座口径,不复写数值)。验收:机检〈三区结构常驻〉
- **栅格规则(候选)**:面板间距/内边距按 4px 步进档(8/12/16/24 候选),全 UI 恒定;数据区直角细线不占圆角预算。终值归样张。验收:目检〈间距落在步进档〉 - **栅格规则(候选)**:面板间距/内边距按 4px 步进档(8/12/16/24 候选),全 UI 恒定;数据区直角细线不占圆角预算。终值归样张。验收:目检〈间距落在步进档〉
### 7.4 字体与字号 ### 7.4 字体与字号
@@ -455,7 +460,7 @@
- **字体**:全局一套像素字,单套即终稿——缝合像素字体 Fusion Pixel(OFL,已接入 `game/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf`),拉丁字符与数字沿用其自带拉丁字形,不另设拉丁专件(现状即终稿;旧拉丁候选 Kenney Fonts 已弃;生产工单=清单 §3.6);墨色走主墨系(§7.2);换字体 = 全局决策。验收:机检〈主题字体指向 Fusion Pixel 件〉 - **字体**:全局一套像素字,单套即终稿——缝合像素字体 Fusion Pixel(OFL,已接入 `game/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf`),拉丁字符与数字沿用其自带拉丁字形,不另设拉丁专件(现状即终稿;旧拉丁候选 Kenney Fonts 已弃;生产工单=清单 §3.6);墨色走主墨系(§7.2);换字体 = 全局决策。验收:机检〈主题字体指向 Fusion Pixel 件〉
- **字号**:像素字体**整数倍缩放,禁非整数缩放**(像素模糊);下限(1280×800 基座):棋盘数字 ≥18px(公式 = `max(18, ceil(格边长 × 0.5))`不变)、**正文/明细 ≥16px、tooltip ≥14px**(1280×800 低于 1080p 基准,同字号实际更小)。验收:机检〈静态文字位点字号 ≥ 档位表值〉 - **字号**:像素字体**整数倍缩放,禁非整数缩放**(像素模糊);下限(1280×800 基座):棋盘数字 ≥18px(公式 = `max(18, ceil(格边长 × 0.5))`不变)、**正文/明细 ≥16px、tooltip ≥14px**(1280×800 低于 1080p 基准,同字号实际更小)。验收:机检〈静态文字位点字号 ≥ 档位表值〉
- **字号档位表(六档)**:全站 UI 文字字号只经 `UiKit.FS_*` 单点取值,裸数字字号弃用——`FS_CAPTION 12`/`FS_HINT 14`/`FS_BODY 16`/`FS_SUB 18`/`FS_TITLE 24`/`FS_H1 34`。旧实测档按就近归档收编(10/11/12→CAPTION、13/14→HINT、15/16→BODY、17/18/20→SUB、21/22/24/26→TITLE、32/40→H1;静态位点全归档,数据驱动动态字号位点除外);棋盘数字公式 `maxi(18, ceil(格边长×0.5))` 与站桩血数 11px(条带内读数豁免)不入表。注:六档相邻比 12→14→16→18 三段 <1.25×,系裁决值优先于字重纪律的档差参考线,该参考线对新层级设计仍有效。验收:机检〈全站无裸数字字号,位点走 FS_*〉 - **字号档位表(六档)**:全站 UI 文字字号只经 `UiKit.FS_*` 单点取值,裸数字字号弃用——`FS_CAPTION 12`/`FS_HINT 14`/`FS_BODY 16`/`FS_SUB 18`/`FS_TITLE 24`/`FS_H1 34`。旧实测档按就近归档收编(10/11/12→CAPTION、13/14→HINT、15/16→BODY、17/18/20→SUB、21/22/24/26→TITLE、32/40→H1;静态位点全归档,数据驱动动态字号位点除外);棋盘数字公式 `maxi(18, ceil(格边长×0.5))` 与站桩血数 11px(条带内读数豁免)不入表。注:六档相邻比 12→14→16→18 三段 <1.25×,系裁决值优先于字重纪律的档差参考线,该参考线对新层级设计仍有效。验收:机检〈全站无裸数字字号,位点走 FS_*〉
- **小字号豁免条款(合并单条不开两口子)**:下款两类**豁免于本节下限**——①**条带内读数**:压在窄条带(血条/进度条等 ≤12px 高带)内居中的数字,受条带高度物理约束,维持 11px 下限,且必须按 §7.2 垫素色牌后计对比(实装=B 区站桩血数 11px 压 HP_BAR_BACK 牌 14.3:1);②**装饰性 overline/caption**:不承载须精确读取的信息、仅作分区标题或氛围小字者,可至 **12px**。**豁免逐处清单(撤除史归 git 提交史,余清单如下)**:条带内读数=B 区站桩血数一处;装饰 caption 档(FS_CAPTION)在册面=棋盘底操作提示条/营地柜台说明文与结构位预留注记/图例 RAP 词条说明/设置警示文两处/关卡地图底栏组标(生命/属性/特性)与分隔系/站桩消散提示/B 区状态槽微计数(×N●○)/bcard 脚注/RAP 反馈行/刷新余量徽标——清单外 11-13px 文字一律按正文/tooltip 下限提档,新增 caption 位须同批增补本清单。**站桩顺位徽标 ①②③ 不增列本清单**:该处原为 13px 字形,既不在①条带内读数在册面、也不在②装饰 caption 在册面,属「清单外 11–13px 文字」,按本条纪律提档至 `FS_HINT` 14 即归六档;为它单独入册等于开第二口子,与「合并单条不开两口子」的立条理由正面相撞。②装饰 caption 在册面逐处维持不变。验收:机检〈豁免文字位均可对上本清单,清单外无 <14px;站桩顺位徽标绘制点取 UiKit.FS_HINT,且②在册面不含该位〉 - **小字号豁免条款(合并单条不开两口子)**:下款两类**豁免于本节下限**——①**条带内读数**:压在窄条带(血条/进度条等 ≤12px 高带)内居中的数字,受条带高度物理约束,维持 11px 下限,且必须按 §7.2 垫素色牌后计对比(实装=B 区站桩血数 11px 压 HP_BAR_BACK 牌 14.3:1);②**装饰性 overline/caption**:不承载须精确读取的信息、仅作分区标题或氛围小字者,可至 **12px**。**豁免逐处清单(撤除史归 git 提交史,余清单如下)**:条带内读数=B 区站桩血数一处;装饰 caption 档(FS_CAPTION)在册面=棋盘底操作提示条/营地柜台说明文与结构位预留注记/图例 RAP 词条说明/设置警示文两处/关卡地图底栏组标(生命/属性/特性)与分隔系/站桩消散提示/B 区状态槽微计数(×N●○)/bcard 脚注/RAP 反馈行/刷新余量徽标——清单外 11-13px 文字一律按正文/tooltip 下限提档,新增 caption 位须同批增补本清单。**站桩顺位徽标 ①②③ 不增列本清单**:该处原为 13px 字形,既不在①条带内读数在册面、也不在②装饰 caption 在册面,属「清单外 11–13px 文字」,按本条纪律提档至 `FS_HINT` 14 即归六档;为它单独入册等于开第二口子,与「合并单条不开两口子」的立条理由正面相撞。**站桩名字行同理提档**:名字行(敌人名与护甲文字)承载须精确读取的信息,原取 12px 不在①②两在册面、属「清单外 11–13px 文字」,按本条纪律提档至 `FS_HINT` 14 归六档、不增列②在册面,名字行几何(素底牌矩形、文字基线、宽度测量)随档位联动。②装饰 caption 在册面逐处维持不变。验收:机检〈豁免文字位均可对上本清单,清单外无 <14px;站桩顺位徽标与站桩名字行绘制点取 UiKit.FS_HINT,且②在册面不含该两位〉
- **字重纪律(字重两档不启用)**:层级手段为两件——**字号档差(相邻层级 ≥1.25×)+ 墨色深浅**;全局单字重,不上第二个字重(**重开钩子已注销**——整屏样张设计轮已重开并复核:屏内各级文字全部能归进本节六档字号,顺位徽标与站桩名字行两处按本节纪律提档即可,屏内实测对比全部达标(暖白对舞台底 9.49:1、GOLD_BRIGHT 6.47:1、血数 14.3:1),**未实证标题层级不足**,维持单字重,不另立条目);数字禁描边/阴影伪加粗不变(字形为唯一数值载体,§4.1 同源纪律)。验收:机检〈主题仅注册单字重〉/目检〈屏内标题与正文层级仅靠字号档差与墨色深浅两件即可分辨,无一处需要第二字重才读得清〉 - **字重纪律(字重两档不启用)**:层级手段为两件——**字号档差(相邻层级 ≥1.25×)+ 墨色深浅**;全局单字重,不上第二个字重(**重开钩子已注销**——整屏样张设计轮已重开并复核:屏内各级文字全部能归进本节六档字号,顺位徽标与站桩名字行两处按本节纪律提档即可,屏内实测对比全部达标(暖白对舞台底 9.49:1、GOLD_BRIGHT 6.47:1、血数 14.3:1),**未实证标题层级不足**,维持单字重,不另立条目);数字禁描边/阴影伪加粗不变(字形为唯一数值载体,§4.1 同源纪律)。验收:机检〈主题仅注册单字重〉/目检〈屏内标题与正文层级仅靠字号档差与墨色深浅两件即可分辨,无一处需要第二字重才读得清〉
### 7.5 营地界面 ### 7.5 营地界面
@@ -511,19 +516,19 @@
- **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉 - **全局纹理过滤与平滑渐变件例外**:`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,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉 - **显示档登记例外(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 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉 - **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉
- **站桩立绘槽位=高定宽随**:立绘/精灵槽位只定「高度的整数倍档」,宽度随源图宽高比自适应——**禁止把全部立绘塞进统一方框强拉**(纵横倍率必须恒等)。反面实例:统一方框把窄条生物源(细长本体裁掉透明边)按主角比例横拉压扁发花。乙类的非整数豁免只覆盖**等比**缩放,不含纵横异倍变形。验收:机检〈立绘纵横倍率恒等〉 - **站桩立绘槽位=高定宽随**:立绘/精灵槽位只定「高度的绝对档(档高取整数像素值)」,宽度随源图宽高比自适应——**禁止把全部立绘塞进统一方框强拉**(纵横倍率必须恒等)。反面实例:统一方框把窄条生物源(细长本体裁掉透明边)按主角比例横拉压扁发花。乙类的非整数豁免只覆盖**等比**缩放,不含纵横异倍变形。验收:机检〈立绘纵横倍率恒等〉
- **1920×1080 档策略**:全局等比(stretch=canvas_items+aspect **keep**)——16:10 设计在 16:9 窗口=等比 1.35×+左右黑边各 ~96px,构图与 1280×800 基准档完全一致;甲类 1.35 非整数缩放例外注记:轻度宽窄不均系本档位接受代价(像素游戏 shipped 常态),实机目检可感不适→回乙案(1920 独立 2× 基准)重议;乙类同前款。验收:目检〈1920 档构图与 1280 档一致〉 - **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× 基准,暂定;与站姿降采样可读性验证一致); - **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致);
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销; - **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(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 区 270/立绘显示上限 188)。验收:机检〈standee_asset_check 逐键档高校验〉 - **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉
- **站桩立绘排版**:① 区高 B 区 270(≈1/3)/C 区 530(≈2/3),**扫雷盘 438×438 原尺寸不缩、格子不变**(区高事实源 battle.md 区表;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:drider/duergar_king)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:55-63`〕,正式舞台背景插画 `scene_stage_bg.png` 到位后该层由背景绘制吸收,**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉 - **站桩立绘排版**:① 区高 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〉 - **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉
### 8.3 导出格式与命名规范 ### 8.3 导出格式与命名规范
- **归档纪律**:图片素材**不入 git 版本管理**——仓库只保留运行时引用资产(game/assets);生成链路原始产物(AI 原图/中间产物/候选版本)归档至 `archive/` 并择机上对象存储备份;主文档(圣经/GDD/清单)**禁引用 artwork/ 散文件路径**——定稿成图以 game/assets 为唯一事实源,过程稿引用一律用归档包路径或描述性出处。验收:机检〈主文档 grep 无 artwork/ 散文件路径〉 - **归档纪律**:图片素材**不入 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 透明底;动画 = 序列帧或引擎精灵表(SpriteFrames),帧尺寸恒定、横向等宽排列(候选)。验收:目检〈交付件透明底无杂边〉
- **命名规范(候选草案)**:`<域>_<对象键>_<状态|变体>[_<序号>].png`,全小写 snake_case;**对象键 = 数据表主键**(effect_id/reward_id/cell_type/enemy_id——图标键契约 §4.6 的文件侧延伸:资产文件名键 = 图标键,全局恒定禁运行时换图);图类型变体后缀 = board_type 键(`_dungeon`/`_underdark`/`_deep_underdark`);候选评审件与终稿分目录(`artwork/candidates/` 承载评审件)。验收:机检〈命名与目录检查器零红〉 - **命名规范(候选草案)**:`<域>_<对象键>_<状态|变体>[_<序号>].png`,全小写 snake_case;**对象键 = 数据表主键**(effect_id/reward_id/cell_type/enemy_id——图标键契约 §4.6 的文件侧延伸:资产文件名键 = 图标键,全局恒定禁运行时换图);图类型变体后缀 = board_type 键(`_dungeon`/`_underdark`/`_deep_underdark`);候选评审件与终稿分目录(`artwork/candidates/` 承载评审件)。验收:机检〈命名与目录检查器零红〉
@@ -552,7 +557,7 @@
| 参考 | 取 | 弃 | | 参考 | 取 | 弃 |
|---|---|---| |---|---|---|
| **Into the Breach** | 格内信息分层、攻击预告上格范式、战棋地块质感骨架(§1.2 原则一;§3.2) | 冷暗军事色调、压暗信息板、斜切 HUD(原则二;§3.3) | | **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) | | **Peglin** | 明快全年龄洞穴色彩、胖体量亲和怪(§5.1 普通怪原型) | 色块依赖——先读轮廓后读颜色(§5.1) |
- **反面条款已成文**(§9.2);反面参考**图证**待本作素材生成后增补。验收:方向性 - **反面条款已成文**(§9.2);反面参考**图证**待本作素材生成后增补。验收:方向性
@@ -561,7 +566,7 @@
| 反面参考 | 违反条款 | 立法理由 | | 反面参考 | 违反条款 | 立法理由 |
|---|---|---| |---|---|---|
| **Darkest Dungeon** 式压暗 | §2 情绪明暗禁令(明度分层下仍禁) | 状态与情绪只走色相/饱和/运动/元素四手段,不以变暗表达情绪(失败重开,世界无光暗记忆) | | **Darkest Dungeon** 式压暗 | §9.2 反面基准(风格取向记录,非禁令——压暗属普通设计决策,逐案走样张与目检) | 失败重开,世界无光暗记忆——明亮基调下不以整屏变暗作常态表达 |
| **传统扫雷白牌/贴纸式数字承载** | §1.2 原则二(明度分档为骨) | 数字由格底明度分档承载、直画入世界;色牌仅复杂格底后备——满屏白牌=电子表格观感,非游戏画面 | | **传统扫雷白牌/贴纸式数字承载** | §1.2 原则二(明度分档为骨) | 数字由格底明度分档承载、直画入世界;色牌仅复杂格底后备——满屏白牌=电子表格观感,非游戏画面 |
| **Enter the Gungeon** 式粒子与色相爆炸 | §3.4 焦点预算;§4.2 色相纪律 | 信息层禁止被特效与杂色吞没,特效入画先过焦点预算 | | **Enter the Gungeon** 式粒子与色相爆炸 | §3.4 焦点预算;§4.2 色相纪律 | 信息层禁止被特效与杂色吞没,特效入画先过焦点预算 |
| **Hades** 式特效密度 | §2.2 兑现爽感的上限 | 爽感靠逐行因果归因,不靠满屏特效;承载元素以 §2.2 清单为界 | | **Hades** 式特效密度 | §2.2 兑现爽感的上限 | 爽感靠逐行因果归因,不靠满屏特效;承载元素以 §2.2 清单为界 |
+293
View File
@@ -0,0 +1,293 @@
# 美术后处理与校验工具清单
> **性质**:本文件是 `tools/` 下美术资产处理与校验脚本的用法清单。回答三件事:这类资产该跑哪件工具、命令怎么敲、判据与坑在哪。
> **边界**:只管工具用法,不立美术规格。色值、画布、体量、命名与验收条款归 [art-bible](art-bible.md)(§4.1 主色板、§8 资产与技术规范)和 [art-assets-checklist](art-assets-checklist.md);命令与源码冲突时以源码现行为准,美术规格冲突时以 art-bible 为准。素材入库与代码接线归 [asset-path-map](asset-path-map.md),不在本文件。
> **分工**:[art-prompts](art-prompts.md) 管角色级提示词与五步出图流程,[camp-scene-prompts](camp-scene-prompts.md) 管营地场景分层提示词与四步流程;两卡只把「后处理」一步指到本文件。提示词怎么写出图、出完图怎么处理,是两件事。
> **命令前提**:以下命令均在仓库根执行。素材不入 git,worktree 缺件时部分脚本回退主检出 `D:/code/g-plan2`,部分直接返回错误,见各节「已知坑与适用边界」。
## 按资产类型选工具
| 资产类型 | 先跑 | 再跑或校验 | 说明 |
|---|---|---|---|
| 角色精灵(八敌、主角) | `tools/pixelize_candidates.py` | `tools/standee_asset_check.py`、`tools/palette_check.py` | 需要收网格时加 `--fixer` |
| 场景不透明全幅(营地背景层、战斗舞台背景) | 无专用工具 | `tools/palette_check.py` 只出读数 | 见「三条已知边界」第 3 条 |
| 场景透明分层(围墙层、八个物件层) | 无专用工具 | `tools/palette_check.py` 只出读数 | 现役批量处理的键表只认角色键 |
| UI 框材(面板、按钮、石框、底噪、图标) | `tools/quantize_ui_skin.py` | `tools/palette_check.py`、`tools/path_check.py` | 主题小件另跑 `tools/gen_theme_icons.py` |
| 地形瓦片 | `tools/recolor_terrain_tiles.py` | `tools/palette_check.py` | 只换色,不重绘 |
| 整屏样张 | `tools/render_sample_sheet_v3.py`、`tools/render_level_map_sheet.py` | `tools/shot_checker.py`;棋盘回归用 `tools/golden_board_crop.py` 加 `tools/golden_compare.py` | 样张脚本生成后自带机检 |
## 三条已知边界
1. **`pixelize_candidates.py` 不能处理任意资产。** `main()` 写死 `SIZES` 角色键表,只遍历这八个键的子目录;目录名不在表里就跳过,没有单文件入口,也没有通用目录模式。
2. **`palette_check.py` 不做色相禁入带检查,也没有全图明度带检查。** 它只统计单张色数,以及对基线判新增色近色;HSV 300°–345° 禁入带与 §4.1 的明度带不在它的检查范围。
3. **现存工具链没有「不透明全幅背景件」的专用入口。** 角色批量处理按角色键,UI 量化按键名,瓦片换色按 `terrain_*_dungeon` 命名,整屏样张是合成器;没有一件接受一张不透明全幅场景图并输出同尺寸成品。`camp-scene-prompts.md` 第 3 步写明背景层不去底,但这一步没有对应的仓库命令。
## tools/pixelize_candidates.py
**一句话定位**:把远程生图的角色候选批量处理成透明底像素精灵;带 `--standee48` 时按逐键档高产出站桩源档。
**归属**:仓库内 `tools/pixelize_candidates.py`。
**输入与输出**:默认批量模式读 `<批次根>/<角色键>/v*.png`,角色键取 `SIZES` 的八个键;输出 `<角色键>/pixel-<n>.png`、四联底测试条 `pixel-<n>-strip.png`,批次根再出一张 `0-像素化预览.png`。站桩模式读 `<批次根>/<角色键>/pixel-1.png` 与 `<批次根>/hero-ref/edit/normal_1x.png`,有 `attack_1x.png`、`hit_1x.png` 时一并产出;输出 `character_enemy_<键>.png`、`character_hero_portrait.png`、`character_hero_attack.png`、`character_hero_hit.png`。
**用法命令**:
```bash
python tools/pixelize_candidates.py <候选批次目录>
python tools/pixelize_candidates.py <候选批次目录> --fixer
python tools/pixelize_candidates.py <候选批次目录> --standee48
python tools/pixelize_candidates.py <候选批次目录> --standee48 --standee-out game/assets/art/character
python tools/pixelize_candidates.py <候选批次目录> --fixer --fixer-exe D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe
```
**判据或口径**:`SIZES` 是中间图最长边,不是最终体量——goblin/giant_spider/hook_horror 96,bugbear/duergar 128,ogre_chief/drider/duergar_king 192。`STANDEE_TIER_H` 是站桩档高——goblin 36、giant_spider 38、duergar 46、hook_horror 58、bugbear 62、ogre_chief 74、drider 90、duergar_king 94;主角取 `STANDEE_SLOT_H` 48。站桩源宽上限 75,脚贴画布底缘。去底走奶油描边屏障法,屏障法保留面积低于底色门路径的 60% 时自动回落;随后吃描边。像素化顺序:内容裁剪、BOX 预滤到 4 倍、中位切分量化 64 色(`QUANT_COLORS`)、NEAREST 降到目标、alpha 二值(阈值 128)、删小于 10px 的孤立不透明域、边缘色内插 2 遍。四联底测试条的四底色在 `STRIP_BGS`。正常结束退出 0;源码没有显式退出码,未捕获异常按 Python 默认非零。
**已知坑与适用边界**:键表固定,任意资产不能直接跑,见「三条已知边界」第 1 条。候选文件名须以 `v` 开头且不含 `pixel`;命名解析只取 `v` 后一位数字,`v10.png` 会与 `v1.png` 落到同一个 `pixel-1.png`。`--standee48` 分支读完 `pixel-1.png` 后直接返回,`--fixer` 在站桩分支不生效;要先跑一次带 `--fixer` 的批量模式。`--fixer` 调用失败只打印一行并回退原图,批次继续,不会以非零退出码中断。去底依赖奶油描边;描边被压暗开缺口会漏灌,脚本按 60% 面积阈值回落,但仍假定底色均匀浅色。输出是角色精灵,不处理不透明全幅件。
## 外部工具:pixel-art-fixer
**一句话定位**:检测伪像素网格并把图重建为原生分辨率的真像素图;是 `--fixer` 的收网格后端。
**归属**:仓库外 `D:/code/pixel-art-fixer`,Retro Diffusion 开源,MIT。可执行文件 `D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe`;`pixelize_candidates.py` 的默认路径同此,可用 `--fixer-exe` 覆写。
**输入与输出**:PNG 或 JPEG,最短边不小于 16px、总像素不超过 4MP。`process` 输出原生 1x PNG,并在 stdout 打一行 JSON,含 `cols`、`rows`、`step_x`、`step_y`、`consensus`、`mode` 与耗时。透明按每格多数保留。
**用法命令**:
```bash
D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe process <输入.png> <输出.png> full
D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe process <输入.png> <输出.png> fast
D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe process <输入.png> <输出.png> full legacy
D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe recon <输入.png> <step_x> <step_y> <cols> <rows> <输出.png>
D:/code/pixel-art-fixer/rust/target/release/pixelfixer.exe full <输入.png>
```
**判据或口径**:`process` 做自动检测加重建,默认重建走 `two_stage_pack`——量化只用于决定结构,颜色取原图对应像素;加 `legacy` 改走旧的 mode-vote 重建。`recon` 是已知网格参数只做重建,默认 raw 不量化,传 `palette` 才启用自动色板,另可传 `dark`。`full` 与 `fast` 只检测不写图,逐图打 JSON;`full` 跑完整仲裁,`fast` 只跑便宜检测器。退出码:成功 0;`process` 输入不合法或写图失败 1;用法错误或未知命令 2。
**已知坑与适用边界**:输出常是几十到几百像素的原生图,显示必须 NEAREST 放大。它是收网格器,不是降噪或调色器;色数与色板仍归 `palette_check.py` 与 `standee_asset_check.py`。`recon` 不检测网格,参数给错就按错的网格重建。`pixelize_candidates.py` 只在 `--fixer` 时调用 `process`,失败打印一行后回退原图。仓库内的可执行文件是本地构建产物;换机器要按 README 的 Rust 用法执行 `cargo build --release`。
## tools/standee_asset_check.py
**一句话定位**:站桩素材硬闸,逐件校验档高、宽度、底锚、色数、alpha 二值与描边亮度。
**归属**:仓库内 `tools/standee_asset_check.py`。
**输入与输出**:默认读 `game/assets/art/character/` 下固定 11 件——八敌 `character_enemy_<键>.png`,主角 `character_hero_portrait.png`、`character_hero_attack.png`、`character_hero_hit.png`。输出 PASS/FAIL 文本,或 `--json` 机器可读结果。
**用法命令**:
```bash
python tools/standee_asset_check.py
python tools/standee_asset_check.py game/assets/art/character
python tools/standee_asset_check.py game/assets/art/character --json
```
**判据或口径**:档高逐键取自 `pixelize_candidates.STANDEE_TIER_H`,主角三态 48。源宽不大于 75,即乘 2 后不超站桩盒宽 150。画布底行至少一个不透明像素,即脚贴底缘。色数不大于 32,只统计 alpha>0 的唯一 RGB。alpha 只允许 0 或 255,出现中间值即判非整数倍缩放痕迹。敌人轮廓像素中暖白描边占比不低于 40%;暖白指亮度不低于 150 且饱和不高于 0.35。主角三态豁免描边断言。全过 0,任一缺失或红灯 1。
**已知坑与适用边界**:主角三态描边实测约 0.4% 暖白,属暗边已知缺口,本闸豁免、不判红;要验主角描边得另找证据。文件名必须与固定清单完全一致,缺一件即失败;目录里多出的 PNG 不检查。它只查站桩 11 件,不覆盖棋盘小人、场景层、UI 或瓦片。色数口径必须排除全透明像素;按所有像素计会假报 33 色。
## tools/palette_check.py
**一句话定位**:扫目录内 PNG,报单张色数与新增色,并按基线色板判新增色近色。
**归属**:仓库内 `tools/palette_check.py`。
**输入与输出**:递归读目录下所有 `.png`;可选 `--baseline` 目录作为基线色板。输出逐张 PASS/WARN/FAIL、小计与累计新增色。
**用法命令**:
```bash
python tools/palette_check.py <待检目录>
python tools/palette_check.py <待检目录> --baseline <基线目录>
```
**判据或口径**:单张色数等于 alpha>0 的唯一 RGB 数,排除全透明。无 `--baseline` 时是只读模式,只出读数、不判红。有基线时,本图颜色减去基线颜色并集得到新增色;新增色与基线任一色的最大单通道差小于 8/255 即判近色红灯。48 色是设计色板 token 上限,不是单张 PNG 的色数;单张 32 色冻结由 `standee_asset_check.py` 执法,本工具的单张色数只报读数。退出码:0 通过,1 有新增色红灯,2 没给目录。
**已知坑与适用边界**:近色只判新增色对基线,不做存量色两两互查。不做色相禁入带检查,也没有全图明度带检查,见「三条已知边界」第 2 条。单张色数超过 32 只标 WARN 或读数,不进退出码;文件头写的「超单张上限判红」与现行源码不一致,按源码为准。文件头提到 `--tokens`,但现行 `main()` 没有这个参数分支,`TOKEN_MAX=48` 目前只是常量。基线目录不存在时不会返回错误:基线色集为空,新增色判据失效。只排除 alpha=0 的像素,半透明像素的 RGB 也计入色数。
## tools/quantize_ui_skin.py
**一句话定位**:把 UI 复用包里的面板、按钮、石框、底噪与图标按 token 明度带量化改色,产出 `game/assets/art/ui/` 下的终稿件。
**归属**:仓库内 `tools/quantize_ui_skin.py`。
**输入与输出**:源包固定为 `artwork/ui-reuse-2026-09-23/` 下的 Kenney 与 OGA CC0 件;当前仓缺位时回退主检出 `D:/code/g-plan2`。输出 14 件到 `game/assets/art/ui/`——5 件框材与底噪、4 枚营地图标、5 枚节点图标。
**用法命令**:
```bash
python tools/quantize_ui_skin.py
```
**判据或口径**:面板、按钮、石框与底噪按五条硬编码 ramp 做明度重映射,保留 alpha 与纹理结构;ramp 常量在脚本顶部 `RAMP_*`。图标走单色重着色:钥石取奖励金,柜台四件取暖白;节点五件取暖白并烘到 20×20。节点图标自检三项——全像素只能是 `NODE_WARM` 或透明、尺寸 20×20、两两 alpha 剪影错位率不低于 0.08;失败 `SystemExit(1)`。复用闸四道(量化、禁入带、灰度可辨、CC0 授权)的来源说明在文件头;脚本实际自检只跑节点图标这三项,框材件没有逐件断言。全过 0,失败 1,源件缺位抛 `FileNotFoundError`。
**已知坑与适用边界**:源包路径、子目录名与文件名全部硬编码;换包或改名直接 `FileNotFoundError`,不是通用量化器。源包在 `artwork/`,整目录不入 git;worktree 或新检出缺件时才会回退主检出,主检出也没有就失败。只服务这一批 UI 复用件,不处理角色、场景或瓦片。输出覆盖同名件;改色值要改脚本顶部 ramp 常量后重跑,再让 Godot 重导入。
## tools/recolor_terrain_tiles.py
**一句话定位**:把 dungeon 地形瓦片按家族与亮度秩序换色,产出 underdark 与 deep_underdark 两套。
**归属**:仓库内 `tools/recolor_terrain_tiles.py`。
**输入与输出**:默认源与输出都是 `game/assets/art/board/`;只匹配 `terrain_*_dungeon.png` 与 `terrain_*_dungeon_<数字>.png`。输出把文件名里的 `_dungeon` 换成 `_underdark` 或 `_deep_underdark`。
**用法命令**:
```bash
python tools/recolor_terrain_tiles.py
python tools/recolor_terrain_tiles.py --palettes underdark
python tools/recolor_terrain_tiles.py --src <源目录> --out <输出目录> --palettes underdark,deep_underdark
```
**判据或口径**:源图每个唯一色先按 RGB 距离归到 dungeon 锚槽的四家族——墙顶、立面、地面、雷坑;再在家族内按亮度秩序线性映射到目标色表同家族槽列。目标色表 `PALETTES` 硬编码在脚本里,槽值来源为归档包 `archive/归档-terrain-walls-2026-09-26.zip` 内的生成器。映射确定性,无随机;脚本打印每枚源图的唯一色数。成功 0;源目录没有匹配瓦片时打印「零输出」并返回 1。
**已知坑与适用边界**:只做换色,不重绘、不增删结构;结构件缺件不能用它补。家族归属靠最近锚色距离,偏离锚槽的颜色可能归错家族。只认 dungeon 命名,其他图类型或其他前缀不处理。默认输出覆盖同目录同名件;源瓦片不入 git,跑前确认盘上源件在。
## tools/gen_theme_icons.py
**一句话定位**:程序化生成 UI 主题小位图——滑轨柄三态、SpinBox 上下箭头、OptionButton 箭头、勾选框四态。
**归属**:仓库内 `tools/gen_theme_icons.py`。
**输入与输出**:无输入参数;输出 9 张 PNG 到 `game/assets/art/ui/gen/`。
**用法命令**:
```bash
python tools/gen_theme_icons.py
```
**判据或口径**:色值取 `game/presentation/views/ui_kit.gd` 的既有常量,脚本头注明溯源;不新增美术素材、不改 art-bible §7。脚本幂等,重复执行覆盖同名件。滚动条柄不走位图,由 `StyleBoxFlat` 承载,本脚本不产出。无显式退出码;正常结束 0,写入异常按 Python 默认非零。
**已知坑与适用边界**:脚本没有验收断言,生成完要目检并让 Godot 重导入。改色值只能改脚本顶部常量再重跑,脚本不读 `ui_kit.gd` 的运行时值。只出主题小件,不处理面板、图标或场景。产出在素材目录,不入 git。
## tools/shot_checker.py
**一句话定位**:整屏截图机检,判屏级骨架、文字对比、背景纹理、明度三档带与文字是否还在框内。
**归属**:仓库内 `tools/shot_checker.py`。
**输入与输出**:截图目录,默认屏清单 `shot_menu.png`、`shot_camp.png`、`shot_level.png`、`shot_battle.png`;同目录 `shot_meta.json` 提供取样框。输出逐项 PASS/FAIL 摘要。
**用法命令**:
```bash
python tools/shot_checker.py <截图目录>
python tools/shot_checker.py <截图目录> shot_menu.png shot_battle.png
```
**判据或口径**: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×」说明不一致,按源码为准。焦点预算是目检项,机检不判。
## tools/golden_board_crop.py
**一句话定位**:按 `board_shot_probe` 输出的 `board_meta.json` 从整屏截图裁出棋盘区,供 golden 逐像素比对。
**归属**:仓库内 `tools/golden_board_crop.py`。
**输入与输出**:三个参数依次是 `board_meta.json`、截图目录、输出目录;读截图目录内 `board_<tag>.png`,按 meta 矩形裁剪,写输出目录同名文件。
**用法命令**:
```bash
python tools/golden_board_crop.py <board_meta.json> <截图目录> <输出目录>
```
**判据或口径**:meta 键即 tag,矩形字段为 x/y/w/h;越界按图像边界收拢。meta 缺失、空表、截图缺失、矩形字段非法或退化,均返回 1;全部裁出 0。禁硬编码像素:布局改动只重跑探针刷新 meta。
**已知坑与适用边界**:依赖 `board_shot_probe` 的 meta 与截图命名;手工截图或改名不认。只裁剪不缩放,输出尺寸随 meta 矩形。它不跑比对;比对是 `golden_compare.py`。
## tools/golden_compare.py
**一句话定位**:黄金图与实测截图逐屏灰度对比,报视觉回归。
**归属**:仓库内 `tools/golden_compare.py`。
**输入与输出**:黄金图目录、新截图目录、可选屏清单;默认四屏 `shot_menu.png`、`shot_camp.png`、`shot_level.png`、`shot_battle.png`。输出逐屏 changed 占比与 PASS/FAIL。
**用法命令**:
```bash
python tools/golden_compare.py game/tests/golden <截图目录>
python tools/golden_compare.py game/tests/golden <棋盘裁剪目录> board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png
```
**判据或口径**:两图尺寸必须一致;逐像素灰度差大于 16 记为 changed。changed 占比超过 2% 判失败;尺寸不符、缺基线或缺截图也判失败。全过 0,有失败 1。
**已知坑与适用边界**:只比灰度,不比色相;纯换色但明度相同可能漏过。基线在 `game/tests/golden/`,资产换代要显式重制并按该目录 README 记录判定,不能拿重制基线掩盖回归。默认四屏不含棋盘与关卡图;比棋盘要传裁剪后的文件名,比关卡图要传 `level_map.png`。截图分辨率必须与基线一致,基线是 1280 档。
## tools/render_sample_sheet_v3.py
**一句话定位**:战斗一屏样张的当前生成器,出 1280×800 与 1920×1080 两版,并在生成过程中跑断言。
**归属**:仓库内 `tools/render_sample_sheet_v3.py`。
**输入与输出**:读 `game/assets/art/board` 的 21 枚瓦片、`game/assets/art/character` 的小人与立绘、`game/assets/ui/icons` 的状态图标;素材缺位回退主检出。输出 `artwork/mockups/sample-batch-v3-final-2026-09-30.png`、`sample_battle_screen_1280x800.png`、`sample_battle_screen_1920x1080.png`;有基线时再出 `sample_battle_screen_before_after.png`;机检报告写到 `reports/`。
**用法命令**:
```bash
python tools/render_sample_sheet_v3.py
python tools/render_sample_sheet_v3.py --save-baseline
python tools/render_sample_sheet_v3.py --gen-assets
```
**判据或口径**:生成即验。F 段管方案甲拼装、旗位、脚锚、0 格字形、微光带限、立面双明度带、悬崖同源;A 段管色板对表、禁入带、周界闭合、明度对比、混排、净空、观感密度;整屏扩展含 S1–S4、P1–P5、V1、V10、A5b。全绿 0,有红 1,报告逐项列 PASS/FAIL。分区几何不设脚本常量:B 区高 / C 区宽 / AP 条高 / 明细带两态高经 `tools/layout_config.py` 读 `game/data/tables/battle_layout.tres`,与游戏侧 `ConfigDb.presentation.battle_layout` 同源。`--gen-assets` 产四件——`scene_stage_plaza.png` 48×12 与 `ui_armor_light`、`ui_armor_ward`、`ui_armor_heavy` 三枚 24×24;尺寸或色数不过直接 `RuntimeError`。`--save-baseline` 只把当前整屏存成基线并返回 0,整屏断言不跑,板面断言即使有红也不改退出码。
**已知坑与适用边界**:固定输出文件名带 2026-09-30,重跑覆盖同名件。字体硬编码 `C:\Windows\Fonts\msyh.ttc`,非 Windows 环境起不来。第一次跑要先 `--save-baseline`,否则 V10 因缺基线判红。素材不入 git;worktree 缺件会抛 `FileNotFoundError`,回退主检出后仍缺也失败。它产的是样张与机检报告,不是游戏内资产;唯一例外是 `--gen-assets` 的四件。
## tools/render_level_map_sheet.py
**一句话定位**:关卡画面样张生成器,出岔路、入口、单链与换层示意,并跑断言。
**归属**:仓库内 `tools/render_level_map_sheet.py`。
**输入与输出**:读 `artwork/ui-reuse-2026-09-23/` 的节点图标与字体、`game/assets/art/ui/backdrop_stone.png`;画布公式、停点标题与 `HIT_PAD` 从 `game/presentation/views/level_map_screen.gd` 解析。输出 `artwork/mockups/level-map-sheet-v1-2026-09-28.png`;机检报告写到 `reports/`。
**用法命令**:
```bash
python tools/render_level_map_sheet.py
```
**判据或口径**:断言 L1 图标互辨、L2 四态与 Boss 环灰度、L3 对比不低于 4.5:1、L4 禁入带零命中、L5 画布自适应、L6 独立屏底、L7 占位区不侵入;另有 SRC 同源、ST 态齐备、S3 换层。全绿 0,有红 1,报告逐项列 PASS/FAIL。节点图标 20px、通道线宽 2/3/4px 等冻结值在脚本顶部;画布公式不自立,解析自 `level_map_screen.gd`。
**已知坑与适用边界**:依赖 `level_map_screen.gd` 存在且常量写法可解析;改常量写法会让脚本抛 `FileNotFoundError` 或解析失败。字体同样硬编码 Windows 字体。固定输出文件名带 2026-09-28,重跑覆盖。样张是验收图,不是游戏资产。
## tools/render_sample_sheet.py
**一句话定位**:战斗一屏样张 v2 生成器,现行 v3 的前一版。
**归属**:仓库内 `tools/render_sample_sheet.py`。
**输入与输出**:读同一批游戏素材,输出 `artwork/mockups/sample-batch-v2-2026-09-28.png`;机检报告写到 `reports/`。
**用法命令**:
```bash
python tools/render_sample_sheet.py
```
**判据或口径**:断言 F1–F7 与 A1–A6;全绿 0,有红 1。产物为 1280×800 单版,没有 v3 的 1920 档、整屏 S/P 段与改前改后并排。
**已知坑与适用边界**:已被 `render_sample_sheet_v3.py` 取代,只用于复现 v2 旧口径,不要拿它出新冻结。固定输出名与报告名带 2026-09-28,重跑覆盖。字体与素材依赖同 v3。
## tools/path_check.py
**一句话定位**:扫描 `asset-path-map.md` 声明的目录与现役文件,以及代码里的 `res://assets/art` 引用,报悬空与退役命名。
**归属**:仓库内 `tools/path_check.py`。
**输入与输出**:可选 `--repo` 仓库根、`--code` 代码根,默认 `.` 与 `game`。输出 L1 目录、L2 文件、L3 代码引用三段 PASS/FAIL。
**用法命令**:
```bash
python tools/path_check.py
python tools/path_check.py --repo . --code game
```
**判据或口径**:L1 查 `asset-path-map` §1 的声明目录,`design/art/candidates` 按需创建豁免。L2 查 `game/assets/art` 的四个语义子目录与现役文件清单,含主角 idle/walk/portrait、八敌、铲三帧、封印印记。L3 grep 代码里的 `res://assets/art` 引用,核对盘上存在性,并检出 `board_floor`、`board_wall`、`pillar_dungeon`、`production/mockups` 等退役命名;路径模板含 `%s` 或 `%d` 的跳过。worktree 素材未同步时,缺失项标 SKIP 不判红;主区完整素材环境才全量判。全过 0,有红灯 1。
**已知坑与适用边界**:依赖 `asset-path-map.md` 的声明清单与代码常量;新增素材目录不更新文档就不会被查。素材不入 git,worktree 空或半同步会大量 SKIP,不能据此判断主区健康。只核对路径存在与退役命名,不核对图像内容、尺寸或色板。
+40 -82
View File
@@ -1,7 +1,7 @@
# 生图提示词卡(角色级资产)v2.11 # 生图提示词卡(角色级资产)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 为准。 > **性质**:美术清单生图通道(✦ 用户自跑,现走线上 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),本文件不覆盖**;两卡的角色/场景分工以「谁的资产谁出卡」为界,同一段提示词不两处维护。战斗舞台的场景级两件与护甲类型图标三枚的**出图工单**登记在本文件 §5(camp-scene-prompts 只管营地场景图,战斗屏这两类无处可落);§5 只把各权威节的定值收进逐件工单,不立新值。意图动画三档是动画资产,等一屏样张锁方案后另立规格卡,本文件不覆盖。 > **边界**:本文件只管**角色级**资产的生图通道。图标/格面/样张走脚本管线(⚙ 代理出),规格在 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 只把各权威节的定值收进逐件工单,不立新值。意图动画三档是动画资产,等一屏样张锁方案后另立规格卡,本文件不覆盖。
> **状态**:提示词卡成文,出图未开始;按「美术圣经 §5 现行」重生成(正序:B 路线),继承 seed 纪律 / 色相禁入带 / `#F2F2F2` 生成底色;沿革见 git 提交史。 > **状态**:提示词卡成文,出图未开始;按「美术圣经 §5 现行」重生成(正序:B 路线),继承 seed 纪律 / 色相禁入带 / `#F2F2F2` 生成底色;沿革见 git 提交史。
--- ---
@@ -11,7 +11,7 @@
1. **出图**:按下面卡片把正向/负向提示词粘进生图渠道的提示词栏(**渠道 = 远程对话式生图为主,本地 ComfyUI 为备选**——2026-10-06 拍板;对话操作纪律见 [remote-gen-prompts](remote-gen-prompts.md)),每个资产出 **2–3 版候选**(建议固定 3 个 seed 连拍,便于横评)。 1. **出图**:按下面卡片把正向/负向提示词粘进生图渠道的提示词栏(**渠道 = 远程对话式生图为主,本地 ComfyUI 为备选**——2026-10-06 拍板;对话操作纪律见 [remote-gen-prompts](remote-gen-prompts.md)),每个资产出 **2–3 版候选**(建议固定 3 个 seed 连拍,便于横评)。
2. **入库**:候选原图(带底)放 `artwork/candidates/<资产名>/`,文件名 `v1.png / v2.png / v3.png`。这个目录只存候选,不进引擎。 2. **入库**:候选原图(带底)放 `artwork/candidates/<资产名>/`,文件名 `v1.png / v2.png / v3.png`。这个目录只存候选,不进引擎。
3. **裁决**:你按 §4 验收速查预筛,挑出每一资产的入围候选拍板。 3. **裁决**:你按 §4 验收速查预筛,挑出每一资产的入围候选拍板。
4. **后处理**:喊我一声,脚本管线代跑——去底转透明、最近邻降采样到目标尺寸、色板量化、四联底测试条(掩尘底/裸土底/暗底/灰度)。 4. **后处理**:喊我一声,脚本管线代跑——去底转透明、最近邻降采样到目标尺寸、色板量化、四联底测试条(掩尘底/裸土底/暗底/灰度)。后处理工具清单与用法见 [art-pipeline-tools](art-pipeline-tools.md)。
5. **转正**:终稿从 `artwork/candidates/` 转入切片资产目录,随表现层升级批接线;清单状态列改「已交付」。 5. **转正**:终稿从 `artwork/candidates/` 转入切片资产目录,随表现层升级批接线;清单状态列改「已交付」。
**出图通道(2026-09-23 定稿)**:线上 API,用户自跑——本地 ComfyUI 生图路线已经用户拍板**彻底关闭**(母版工作流/像素 LoRA/Z-Image 模型已清退,不留复活预案,本卡不依赖任何本地管线)。 **出图通道(2026-09-23 定稿)**:线上 API,用户自跑——本地 ComfyUI 生图路线已经用户拍板**彻底关闭**(母版工作流/像素 LoRA/Z-Image 模型已清退,不留复活预案,本卡不依赖任何本地管线)。
@@ -286,50 +286,15 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
## 5. 资产出图工单(四组九件) ## 5. 资产出图工单(四组九件)
> **范围注**:本文件原定位角色级(见文件头边界句),§4bis 已有「地形类同库登记」的先例,本节循同一先例补登记战斗屏两类——战斗舞台的场景级两件(① ②)与护甲类型图标三枚(③),两组在营地卡与清单里都无处落工单;角色级立绘重产(④)是本文件的本职。四组的规格定值清单 §4 已有登记,本节补的是「逐件照着就能出件」那一层。 > **范围注**:本文件原定位角色级(见文件头边界句),§4bis 已有「地形类同库登记」的先例,本节循同一先例补登记战斗屏的**小石台件(②)**与护甲类型图标三枚(③),两组在营地卡与清单里都无处落工单;战斗舞台背景层(①)与连续地面层的元素/提示词已另立 [battle-stage-prompts](battle-stage-prompts.md),§5.1 只留指路;角色级立绘重产(④)是本文件的本职。四组的规格定值清单 §4 已有登记,本节补的是「逐件照着就能出件」那一层。
> **定值纪律(§8 头注 定值单点纪律)**:本节**指路不立值**——色值 = 圣经 §4.1(含场景环境子板)、尺寸与缩放 = §8.2、明度带 = §2、甲档 = character-design §5.3、命名 = §8.3 + asset-path-map。本节新增零定值;凡圣经未定的值,一律进本节 §5.5 缺口,不在本节填空。 > **定值纪律(§8 头注 定值单点纪律)**:本节**指路不立值**——色值 = 圣经 §4.1(含场景环境子板)、尺寸与缩放 = §8.2、明度带 = §2、甲档 = character-design §5.3、命名 = §8.3 + asset-path-map。本节新增零定值;凡圣经未定的值,一律进本节 §5.5 缺口,不在本节填空。
> **验收标注纪律**:逐条按三档标 `←机检: <断言>` / `←目检: <判据>` / `←方向性`,照圣经头注 2026-10-06 用户拍板的写法。 > **验收标注纪律**:逐条按三档标 `←机检: <断言>` / `←目检: <判据>` / `←方向性`,照圣经头注 2026-10-06 用户拍板的写法。
> **流程**:出图前后一律走 §0 五步;出图登记义务照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,其余参数有则记),随候选图存 `artwork/candidates/<资产名>/`;登记不作正稿遴选前置。 > **流程**:出图前后一律走 §0 五步;出图登记义务照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,其余参数有则记),随候选图存 `artwork/candidates/<资产名>/`;登记不作正稿遴选前置。
> **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`scene_stage_plaza.png` 48×12、`ui_armor_light/ward/heavy.png` 24×24,路径见各组 ① 字段;登记=战斗一屏样张批,合并提交 3b66d1a6);组一背景插画与组四立绘重产仍待出图。 > **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`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 件) ### 5.1 组一 · B 区舞台背景插画(1 件)
| 字段 | 值 | > **本件已迁出本文件**:逐条要素、分层提示词、否定后缀与验收 = [battle-stage-prompts](battle-stage-prompts.md)(背景层 §2.1/§4);场景构成与图层归属 = [art-bible](art-bible.md) §6.1;尺寸读 `game/data/tables/battle_layout.tres`。本文件不再复写本件提示词,避免两处维护。
|---|---|
| ① 路径与命名 | `game/assets/art/scene/scene_stage_bg.png`——域键 `scene`(asset-path-map §3),命名照 §8.3 `<域>_<对象键>_<状态\|变体>[_<序号>].png`。**三关不按板出三张,文件名不带 `_dungeon`/`_underdark`/`_deep_underdark` 后缀**(三关换挡走 §4.3 `wall_ambient` tint 槽,与棋盘同款换皮逻辑) |
| ② 尺寸档与像素网格 | 源 1280×270(B 区全高含明细带底),显示 1×;九宫格 = 否。甲/乙两分类不覆盖环境件(§8.2 甲类 = 文本与语义件、乙类 = 角色与单位精灵),本件在 1280×800 基座下满幅铺 1×、不缩放;1920 档口径见 §8.2「1920×1080 档策略」条,本节不另立值 |
| ③ 色板约束 | 进**战斗屏场景环境子板**(§4.1 分层②:每屏一套 ≤32 色、跨屏不共享、**不占 48 token 额度**——48 额度余量现为 0(2026-10-28 用尽),本件不动它)。**零新增**——主体三色:远景最深 `#282220`(默认;备选 `#3A2E22`,二选一,选后者则图内禁用 `#362B20`,两者三通道差 4/3/2 全部 <8,是 §4.1 欠账清单六对之一);中景洞壁 `#3E362C` 与中景受光壁面 `#453723` 交替,**两色只靠笔触与纹理分出块面,不靠第三个色**;需过渡色时只从在册的 `#362B20`/`#4C4038` 取。单张 PNG 像素域色数 ≤32。第二道门 = 与既有任一色三通道差不得全部 <8/255(§4.1 增量纪律),不过门即换色合并、禁加色绕行 |
| ④ 明度带归属 | **世界底层下沿子带 L 0.014–0.06**(§2 世界底层两子带)——全图落该子带,不得越进上沿子带 L 0.08–0.14(那是已开格底与迷雾记忆态合成底的地盘)。三色实测: `#282220` L≈0.016 / `#3E362C` L≈0.039 / `#453723` L≈0.042 |
| ⑤ 是否程序化件 | 否 → **须出图**(✦ 插画级,清单 §4 已定来源 = 作者自跑远程生图管线)。现行程序化平色 + 光柱 = 占位(◐),按 §7.1 占位完成条款须在终稿闸前换代 |
| ⑥ 验收判据 | 见下方逐条判据 |
| ⑦ 出图操作要点 | 见下方提示词与负面约束 |
**逐条判据**
- 构图只出远景 + 中景两带:远景 0–35%、中景 35–78%,78%–100% 段不画 ←机检: 画布 1280×270 逐位相符;`y ≥ 210`(78%) 段内 `#968264` 像素数 = 0 且该段与中景段无独立地台结构件
- 三关只走 tint 槽 ←机检: 仓内 `scene_stage_bg*` 文件名无 board_type 后缀变体(三关方向指路 §6.3,该节终值未冻结,属实装批,不阻塞本件出图)
- 色板零新增 ←机检: 单张像素域色数 ≤32 且逐色在 §4.1 战斗屏场景环境子板在册
- 明度带 ←机检: 全图取样点相对亮度 L 全部落 [0.014, 0.060]
- 禁入带 ←机检: 全图无 HSV 色相 300°–345° 像素(清单 §0 复用闸②吸色检查同口径)
- 临时光柱层退役 ←机检: 本件入包后 `battle_stage_view.gd` 四柱临时光柱层退役(§8.2 站桩排版③条;该条的立绘溢出判定不随之变更)
- 背景自然后退 ←目检: 立绘与 C 区数字在前,本件不构成完整凸形、无第二焦点(§3.4 焦点预算 / §6.1 低对比后退)
- 气质 ←方向性: 明亮地窟、暖倾向、无压抑(§1 风格基调;§9.2 Darkest Dungeon 式压暗在反面清单)
**出图画布**:2560×540(终稿 2×,保证整数倍降采样;平台不支持该宽高比时取可用的最宽档出图、按上下居中裁到 4.74:1,禁横向拉伸变形)。
**正向提示词(整段可贴)**:
```
Pixel art style. True pixel art game background illustration drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about eight colors, retro 16-bit look, Aseprite style, all ages, bright cheerful fantasy cavern, not dark and not grim, flat side view, orthographic, no perspective distortion, very wide horizontal band about 4.7 to 1 aspect ratio. This is the BACK WALL of a bright underground cavern arena seen straight on, and it is a BACKGROUND ONLY: it must contain no characters, no creatures, no floor platform and no props, because the standing stone platform and the characters are drawn on top of it as separate pieces. TWO BANDS ONLY, and the bottom twenty-two percent of the frame stays completely empty. FAR BAND (the top zero to thirty-five percent of the frame height): the deepest hollow of the cavern, a flat dark far wall receding into shadow, one or two rows of very simple flat stalactite silhouettes hanging from the top edge, the darkest area of the picture, its darkest value is hex color #282220, no light sources, no small detail, everything reduced to a few large flat shapes. MIDDLE BAND (thirty-five to seventy-eight percent of the frame height): the cavern wall, broad flat facets of stone alternating between hex colors #3E362C and #453723, the facets separated only by their brush texture and by a few slightly darker seam lines, with two or three soft vertical light shafts falling from the cave roof across the wall, the shafts being flat low-contrast bands of light and not beams of particles. WHOLE IMAGE: every color is very dark and low in contrast, the entire background recedes behind the characters, there are no bright highlights and no rim lights, no single element forms a complete solid shape that competes for attention, and all wall facets stay large and simple.
```
**负向提示词(整段可贴)**:
```
Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; blur or compression artifacts; a vignette, dark corners, or any mood darkening; any purple or magenta anywhere, any color with a hue between 300 and 345 degrees; any text, letters, numbers, watermark, signature or logo; any frame, border, panel, arrow or UI element; any characters, people, creatures or animals; any floor, ground plane, stone platform, pedestal, stage floor or baseboard in the lower part of the frame, because the bottom twenty-two percent of the frame must stay completely empty; any torch, brazier, lantern, candle or glowing object as an object, only the flat light shafts described above; bright or saturated colors, any area lighter than hex #453723; high contrast edges, rim lights, black outlines; rubble, chains, bones, murals, paintings, furniture or any extra prop.
```
**后处理**:§0 五步第 4 步——本件是**不透明全幅件**,去底与四联底测试条两步不适用,只走最近邻降采样到 1280×270 + 色板量化到本节 ③ 所列色;终稿入包后走 §1.4 整屏样张闸。
### 5.2 组二 · 近景地台件(1 件) ### 5.2 组二 · 近景地台件(1 件)
@@ -343,7 +308,7 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
| ⑥ 验收判据 | 见下方逐条判据 | | ⑥ 验收判据 | 见下方逐条判据 |
| ⑦ 出图操作要点 | 见下方工艺要点(手绘/程序化,无须 AI 生图) | | ⑦ 出图操作要点 | 见下方工艺要点(手绘/程序化,无须 AI 生图) |
**独立成件的两条理由(§6.1「近景地台走独立件」条,写进工单备查)**:① 结算期明细带底 84px 会盖住 B 区 78%–100% 段(约 59px),84 > 59,近景带在结算期整条不可见——画进背景等于画一件永远看不见的东西;② §4.3 `wall_ambient` tint 作用于整张背景,地台若画进背景会随三关换明度,而 §6.1 舞台正面目标把地台定死为世界中层承载面,随关卡变会破 §2 明度分层的层间关系。 **独立成件的两条理由(§6.1「近景地面与地台走独立件」条,写进工单备查)**:① 背景层按关卡出三张变体,地台若画进任一张就得三张各画一遍、且三关站位面会漂移;② 结算期明细带整条压住 B 区底带(带高 = `log_band_h_settle`,读布局表),地台在独立层才能跨关恒定并保持 §6.1 定死的世界中层承载面不变。
**逐条判据** **逐条判据**
@@ -379,7 +344,7 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
- ←机检: 源 24×24、显示 1× 整数倍(§8.2 显示档登记例外条:素材一字节禁改) - ←机检: 源 24×24、显示 1× 整数倍(§8.2 显示档登记例外条:素材一字节禁改)
- ←机检: 文件名逐位等于 `ui_armor_light` / `ui_armor_ward` / `ui_armor_heavy` - ←机检: 文件名逐位等于 `ui_armor_light` / `ui_armor_ward` / `ui_armor_heavy`
- ←机检: 单张像素域色数 ≤32 - ←机检: 单张像素域色数 ≤32
- ←机检: 三枚对其实际挂载底色的图形对比 ≥3:1(§4.1 图标槽柔和档条的图形参考线硬约束)。**挂载底色 = 站桩名字行的素底,不是舞台底也不是地台面**——站桩名字行是文字,§7.2「一切文字(含菜单/说明/tooltip/按钮/底栏,**无区域例外**)」+「文字禁直接压纹理底——垫容器或罩层」两条覆盖它;§6.1 舞台正面目标条(2026-10-28 v3.22 定值)已就站桩文字位显式落法:「站桩文字位改垫素底或落进结算明细带」,并明写「**非新条款**」,§7.2 同批加「站桩石板地台面同理」句、两处机检断言同文。舞台背景插画 `scene_stage_bg.png` 的中景两色只靠笔触与纹理分块面(5.1 ③),属纹理底,落法门触发。**故「站桩名字行落法 = 垫素底」是既有条款的落法,本节不立新条款**(§5 头注定值纪律:本节指路不立值)。素底明暗档 **2026-10-28 已裁 = 深档 `#282220`**(§7.4 血数垫牌 `HP_BAR_BACK` 在册同款,零新增;浅档 `#F5EAD3` 落选,两档对照与理据见 §5.5 缺口 2 已裁条) - ←机检: 三枚对其实际挂载底色的图形对比 ≥3:1(§4.1 图标槽柔和档条的图形参考线硬约束)。**挂载底色 = 站桩名字行的素底,不是舞台底也不是地台面**——站桩名字行是文字,§7.2「一切文字(含菜单/说明/tooltip/按钮/底栏,**无区域例外**)」+「文字禁直接压纹理底——垫容器或罩层」两条覆盖它;§6.1 舞台正面目标条(2026-10-28 v3.22 定值)已就站桩文字位显式落法:「站桩文字位改垫素底或落进结算明细带」,并明写「**非新条款**」,§7.2 同批加「站桩石板地台面同理」句、两处机检断言同文。舞台背景插画(`scene_stage_bg_<board_type>` 三关变体;battle-stage-prompts §2.1)属纹理底,落法门触发。**故「站桩名字行落法 = 垫素底」是既有条款的落法,本节不立新条款**(§5 头注定值纪律:本节指路不立值)。素底明暗档 **2026-10-28 已裁 = 深档 `#282220`**(§7.4 血数垫牌 `HP_BAR_BACK` 在册同款,零新增;浅档 `#F5EAD3` 落选,两档对照与理据见 §5.5 缺口 2 已裁条)
- ←机检: B 区站桩名字行与护甲图标正下方像素非地台板身色 `#968264`(§6.1 / §7.2 同文断言) - ←机检: B 区站桩名字行与护甲图标正下方像素非地台板身色 `#968264`(§6.1 / §7.2 同文断言)
- ←目检: 灰度下按甲面覆盖率三档可辨——无甲板 → 符纹带 → 大面积甲板(character-design §5.3 灰度通道条) - ←目检: 灰度下按甲面覆盖率三档可辨——无甲板 → 符纹带 → 大面积甲板(character-design §5.3 灰度通道条)
- ←目检: 三枚互辨,身份只靠形状不靠色相(§4.5 ⑨ 图标键族:色相不承载身份) - ←目检: 三枚互辨,身份只靠形状不靠色相(§4.5 ⑨ 图标键族:色相不承载身份)
@@ -410,25 +375,27 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
- ←方向性: 冷灰与全作暖底成材质对比(§5.3),不靠明度差与地台 `#968264`(L≈0.234)抢读——立绘与地台的可辨归 §5.1 轮廓通道 - ←方向性: 冷灰与全作暖底成材质对比(§5.3),不靠明度差与地台 `#968264`(L≈0.234)抢读——立绘与地台的可辨归 §5.1 轮廓通道
- 来源与工艺:♻ 素材站 plate armor 肩甲/胸甲类图标改色 → 闸四道;⚙ 兜底 24×24 内肩 + 胸两块甲板,与全作暖底成材质对比(§5.3 冷灰金属与暖底对比) - 来源与工艺:♻ 素材站 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 现役写法) | | ① 路径与命名 | `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 整数档 | | ② 尺寸档与像素网格 | **源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;**显示档 = 三档绝对高**——小 96(地精/巨蜘蛛/灰矮人)、中 144(主角/钩爪恐魔/熊地精)、大 188(食人魔酋长/蛛化卓尔/灰矮人石君王),定值单点 = art-bible §8.2;显示倍率 = 档高 ÷ 源高,NEAREST,宽随源图比例(高定宽随、纵横倍率恒等,禁方框强拉);九宫格 = 否 |
| ③ 色板约束 | 进**语义/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 | | ③ 色板约束 | 色值以**现役稿**为准,本批零新增色、零换色;单张像素域色数 ≤32(`pixel-1` 与手修 2× 稿实测均 ≤32)。`#A79CB8` 保留在板、本批不消费,色板额度议题另批讨论 |
| ④ 明度带归属 | **世界中层 L 0.22–0.45**(§2 世界中层 = 格底顶面 / 角色 / 物件)——判定对象 = 角色体块主色。描边与落影底盘属轮廓通道(character-design §5.1)、紫赤点缀属 §4.2 色相独占的语义色,两者都不按明度带判定 | | ④ 明度带归属 | **世界中层 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 + 脚底基线对齐 + 命名合规) - ←机检: 脚底基线对齐(紧裁 + 底中锚;命名合规、像素域色数 ≤32 同旧)
- ←机检: 主角描边 = `(40,19,13)`;敌方描边取暖白族(非黑、非深褐)—— character-design §5.1 主角描边例外条 - ←机检: 主角描边 = `(40,19,13)`;敌方描边取暖白族(非黑、非深褐)—— character-design §5.1 主角描边例外条
- ←机检: Alpha 直出——不透明区外缘 1px 环内无生成底残留(无 R/G/B 三通道均 ≥230 且 alpha > 0 的像素),防抠图白边 - ←机检: Alpha 直出——不透明区外缘 1px 环内无生成底残留(无 R/G/B 三通道均 ≥230 且 alpha > 0 的像素),防抠图白边
- ←机检: 零 HSV 300°–345° 像素(drider / duergar_king 两 Boss 除外,§4.2 独占) - ←机检: 零 HSV 300°–345° 像素(drider / duergar_king 两 Boss 除外,§4.2 独占)
@@ -436,48 +403,39 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
- ←目检: 放大 4 倍,轮廓与衣服是成组的像素,不是降采样噪点(三判据①) - ←目检: 放大 4 倍,轮廓与衣服是成组的像素,不是降采样噪点(三判据①)
- ←目检: 显示 96–188px 下脸、铲、旗、物种能认出来,不必先读名字(三判据②) - ←目检: 显示 96–188px 下脸、铲、旗、物种能认出来,不必先读名字(三判据②)
- ←目检: 显示仍用邻近采样,不出现插画那种平滑渐变(三判据③) - ←目检: 显示仍用邻近采样,不出现插画那种平滑渐变(三判据③)
- ←方向性: 精细手绘像素气质——不是插画、也不是半像素半高清 - ←方向性: 现有形象即终稿,本批禁改形象
三判据出处 = `reports/美术审计-cursor会话存档-2026-10-14.md` §2.2。 三判据出处 = `reports/美术审计-cursor会话存档-2026-10-14.md` §2.2。
**工艺要点(手绘)** **派生要点(零绘制)**
1. 出件顺序 = 先主角、再三 Boss(Alpha 范围 = 主角 + 三 Boss 重产,其余敌种当前可读则保留到 Beta)。 1. 出件顺序 = 先主角三态,再三 Boss,再其余四敌(同管线,无绘制差异)。
2. **源密度判据 = 三判据②**:显示 96–188px 下脸、铲、旗、物种可认——照这条反推源档即可,逐键定档已完成(§8.2),本批不必再猜档位。 2. 每件 = `pixel-1`/手修 2× 稿紧裁(去透明边、底中锚)→ 原样落 `game/assets/art/character/character_*`;**不做 `dom_block_resize`**。
3. 手绘起稿顺序 = 剪影(§5.1 archetype)→ 体块主色 → 暗部与受光 → 描边 → 落影底盘。 3. 入包前过 §8.3 色板检查器与 standee_asset_check(按 §8.2 显示档校验逐件档高与纵横倍率)。
4. 逐件 2–3 版候选;登记照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,手绘作者有则记),随候选图存 `artwork/candidates/<资产名>/`。 4. 桩盒宽 150 与 >150 溢出档的实装口径归代码批(art-bible §8.2 排版条);食人魔超宽 8px 为已接受项。
5. 候选评审 = 并排横评,**隐去文件名与来源后按三判据逐条判读**(§0 五步第 1 步「便于横评」的手绘侧落法),不靠记忆与印象拍板。
6. 出件 = 透明 PNG(Alpha 直出,禁先出白底图再抠);入包前过 §8.3 色板检查器(报新增色 / 近色告警 / 超 48 上限拦截)。
**5.4.1 主角(源 48 / 显示 96)** **5.4.1 主角(中档 144)**
- 形象 = character-design §5.4 形象锚逐字执行:蓝兜帽取下披肩、暖米白上衣、深棕工装裤、额前护目镜、半指手套、卷裤脚皮护膝、圆头挖掘铲、结构式冒险背包配朱红矩形旗、腰挂提灯 - 形象 = 现役稿即终稿,character-design §5.4 形象锚已兑现于现役素材;朝向沿用现役,不重新裁定
- 已登记色:兜帽 `#2E86DE` / 上衣 `#EFE3C8` / 长裤 `#6B4A2E` / 皮革 `#8A5A34` / 眼 `#33261B` / 旗 `#C63D22` / 描边 `(40,19,13)` - 色 = 以现役稿为准(§5.5 缺口 4 的额度账随色板议题另批处理)
- 已落值(2026-10-28 用户裁):**铲刃浅铁灰 = `FOG_MEMORY_INK #C4C4C4`**;**提灯铜色 = `GOLD_DEEP #B07A14`**——两件均走换色合并、零额度(character-design §5.4 对这两件只有形容词,账见 §5.5 缺口 4)。代价已随裁定接受:`#C4C4C4` 一色两岗(语义岗 = 迷雾记忆墨 §6.5)、`#B07A14` 属金族(§4.2 金只说收益与兑现);两者均为角色随身件材质色,不进信息层与语义物件岗 - 源 = `hero-ref/edit/normal_2x.png`(内容 88×92);attack/hit 用 `attack_2x.png`/`hit_2x.png`
- 破例记账:背包 + 旗 = 主角对 §5.1「对称」的唯一破例组,旗面朝向恒定画右;轮廓凹凸预算按背包 + 旗整组记账
- 朝向:战斗画面的侧身面向怪物形象属状态图(§5.4 生成顺序口径);重产沿用现役朝向,不重新裁定
**5.4.2 食人魔酋长 ogre_chief(源 74 / 显示 148)** **5.4.2 食人魔酋长 ogre_chief(大档 188)**
- 档位 = 精英档(形状仍守精英档:普通怪形 + 恰一处垂直延伸 = 头顶骨与羽冠,底盘不变;第一关关底 Boss 战,不升 Boss 形) - 档位 = 大型(三场 Boss 战全归大型);形状仍守精英档剪影——普通怪形 + 恰一处垂直延伸(头顶骨与羽冠),底盘不变
- 甲档 = 轻甲 → 甲面覆盖率 ≈ 0:皮条 / 骨饰点缀、裸身为主,零甲板零符纹 - 甲档 = 轻甲(皮条/骨饰点缀、裸身为主,零甲板零符纹)——现役稿已如此
- 主色域 = 暖褐皮、圆肚;**暖褐皮 = `暖石 #B8A582`**(2026-10-28 用户裁:换色合并、零额度;体块主色带 §5.4 ④ L 0.22–0.45 校验 L≈0.387 ✓;character-design §5.5 该行原只有形容词) - 源 = `ogre_chief/pixel-1.png`(158×189);显示宽 157 超桩盒 150 为已接受项
**5.4.3 蛛化卓尔精灵 drider(源 90 / 显示 180)** **5.4.3 蛛化卓尔精灵 drider(大档 188)**
- 档位 = Boss(敌类中唯一允许锐角与不对称者,体量最大) - 档位 = 大型;紫赤 `#8F2F6A` 色相独占 + 形状独占双通道维持现役稿
- 甲档 = 魔法护甲 → 符纹饰带(暖白,覆盖率居中);蛛腹几丁质属天然甲壳,不计甲档 - 甲档 = 魔法护甲(符纹饰带)——现役稿已如此
- 独占:紫赤 `#8F2F6A`(色相 323°)+ 形状独占双通道;全项目色相 300°–345° 只许本件与 5.4.4 出现 - 源 = `drider/pixel-1.png`(140×189);皮色 `#A79CB8` 保留在板、本批不消费
- **卓尔上体浅紫灰皮 = `#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 区间定,血条仍落底)
**5.4.4 灰矮人石君王 duergar_king(源 94 / 显示 188)** **5.4.4 灰矮人石君王 duergar_king(大档 188)**
- 档位 = Boss(锐角与不对称,体量最大档) - 档位 = 大型(锐角与不对称);紫赤独占 + 岩石板甲维持现役稿色
- 甲档 = 重甲 → 金属甲板(冷灰,覆盖率最大) - 源 = `duergar_king/pixel-1.png`(133×189)
- 独占:紫赤 `#8F2F6A` + 形状独占双通道;大地 / 岩石主题
- 岩石板甲与灰皮(2026-10-28 用户裁,character-design §5.5 该行原只有形容词):**岩石板甲 = 重甲甲材质色,与 §5.3.3 同一枚 → 直接引 `#868C9A`,不另计额度**;**灰皮 = `STONE #786E62`**(按**次级皮**口径——石君王重甲覆盖率最大、板甲视同体块主色,故灰皮属次级皮、不受 L 0.22–0.45 约束;换色合并、零额度)
- 大体档:显示 188 > 150 → 溢出盒顶同 5.4.3
### 5.5 缺口(四条 · 缺口 1/2/4 于 2026-10-28 已裁转指路,缺口 3 结论已立) ### 5.5 缺口(四条 · 缺口 1/2/4 于 2026-10-28 已裁转指路,缺口 3 结论已立)
@@ -491,7 +449,7 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
- ←机检: 本值入板后全板色对中三通道差全部 <8/255 的对数 = 0 - ←机检: 本值入板后全板色对中三通道差全部 <8/255 的对数 = 0
2. **站桩名字行的落法 + 素底明暗档——已裁(2026-10-28)= 垫素底、素底取深档 `#282220`**,本节转指路(原记为「轻甲木色在深底上不过 3:1」;核出落法是它的前置,故先落这一条)。 2. **站桩名字行的落法 + 素底明暗档——已裁(2026-10-28)= 垫素底、素底取深档 `#282220`**,本节转指路(原记为「轻甲木色在深底上不过 3:1」;核出落法是它的前置,故先落这一条)。
- **推导核实 = 成立,且是既有条款的落法、非新条款**:站桩名字行是文字,§7.2「一切文字(含菜单/说明/tooltip/按钮/底栏,**无区域例外**)」+「文字禁直接压纹理底——垫容器或罩层」两条覆盖它;§6.1 舞台正面目标条(2026-10-28 v3.22 定值)已就站桩文字位显式落法——「站桩文字位改垫素底或落进结算明细带」并明写「**非新条款**」,§7.2 同批加「站桩石板地台面同理」句,两处机检断言同文。舞台背景插画 `scene_stage_bg.png` 中景两色只靠笔触与纹理分块面(5.1 ③),属纹理底 → 落法门触发。**已回填**:站桩名字行落法 = **垫素底**(§5.3 共用验收条 + 5.3.1 / 5.3.3 对比判据)。 - **推导核实 = 成立,且是既有条款的落法、非新条款**:站桩名字行是文字,§7.2「一切文字(含菜单/说明/tooltip/按钮/底栏,**无区域例外**)」+「文字禁直接压纹理底——垫容器或罩层」两条覆盖它;§6.1 舞台正面目标条(2026-10-28 v3.22 定值)已就站桩文字位显式落法——「站桩文字位改垫素底或落进结算明细带」并明写「**非新条款**」,§7.2 同批加「站桩石板地台面同理」句,两处机检断言同文。舞台背景插画(`scene_stage_bg_<board_type>` 三关变体;battle-stage-prompts §2.1)属纹理底 → 落法门触发。**已回填**:站桩名字行落法 = **垫素底**(§5.3 共用验收条 + 5.3.1 / 5.3.3 对比判据)。
- **原冲突 = 素底明暗档圣经未定死(2026-10-28 已由作者裁,本节不替裁的冲突至此闭合)**:§4.1 只在册浅档 `#F5EAD3`(数字色牌,圣经唯一素色牌值),§7.4 血数垫牌在用的深档 `HP_BAR_BACK #282220` 亦在册。两档后果**相反**(下表留作裁定依据账): - **原冲突 = 素底明暗档圣经未定死(2026-10-28 已由作者裁,本节不替裁的冲突至此闭合)**:§4.1 只在册浅档 `#F5EAD3`(数字色牌,圣经唯一素色牌值),§7.4 血数垫牌在用的深档 `HP_BAR_BACK #282220` 亦在册。两档后果**相反**(下表留作裁定依据账):
| 受影响的位 | 浅档 `#F5EAD3` | 深档 `#282220` | | 受影响的位 | 浅档 `#F5EAD3` | 深档 `#282220` |
+3 -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 | | 主角战斗小人(待机/攻击/受击三态) | ✅ 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 本地在盘)。八敌源档已定(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 | | 八敌种形象(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 | | 意图准备动画三档 | ◐ | [art-bible](art-bible.md) §4.6.4 |
## P2 图标与格面(可与样张并行) ## P2 图标与格面(可与样张并行)
@@ -40,6 +40,7 @@
| 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 | | 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 |
| 封印特效(动作=封印,两件:施印特效+印记常显) | ⬜ | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 | | 封印特效(动作=封印,两件:施印特效+印记常显) | ⬜ | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 |
| 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) | | 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) |
| B 区舞台背景三关变体+共用连续地面层(场景件) | ✅ 已交付(2026-10-31 入包 `game/assets/art/scene/`:三张背景 1280×252、共用地面 1280×100;地面按作者裁保留候选 v12b 原亮度 L≈0.132) | [art-bible](art-bible.md) §6.1 · [battle-stage-prompts](battle-stage-prompts.md) §2 |
--- ---
+1 -1
View File
@@ -27,7 +27,7 @@
|---|---|---| |---|---|---|
| `character/` | 角色棋盘帧 + B 区立绘 | character_hero_idle/walk(棋盘 32×32 朝左基准)、character_hero_portrait(B 区立绘 46×48)+ character_enemy_<键> 八敌 | | `character/` | 角色棋盘帧 + B 区立绘 | character_hero_idle/walk(棋盘 32×32 朝左基准)、character_hero_portrait(B 区立绘 46×48)+ character_enemy_<键> 八敌 |
| `fx/` | 特效帧 | shovel_swing_f0/f1/f2(挥铲三帧,握把锚 (18,12)) | | `fx/` | 特效帧 | shovel_swing_f0/f1/f2(挥铲三帧,握把锚 (18,12)) |
| `scene/` | **场景图**(背景层 + 可点物件层;营地场景图首批) | `scene_camp_bg`(背景)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件,不可点)+ `scene_stage_bg`(B 区战斗舞台背景,✦ 插画级,只出远景+中景两带)+ `scene_stage_plaza`(B 区近景地台,48×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 已入包;营地 10 张 2026-11-01 随迭代 47 卡④ 入包(`scene_camp_*` 十件在役、`TEX_CAMP_*` 十键消费;用户游戏内终审如有置换,按本行键名同径替换) |
| `board/` | 砖面/封印印记(三关换皮 + 封印印记件) | terrain_floor_<board_type>_<n>.png(三关地面系)、seal_mark.png | | `board/` | 砖面/封印印记(三关换皮 + 封印印记件) | terrain_floor_<board_type>_<n>.png(三关地面系)、seal_mark.png |
## 4. 坐标/朝向等工程侧约定备查 ## 4. 坐标/朝向等工程侧约定备查
+182
View File
@@ -0,0 +1,182 @@
# 战斗舞台背景 · 分层生图提示词卡 v1.8
> **性质**:B 区战斗舞台背景(顶部空区+远景天然洞壁+近景连续泥土地面+分隔带)的出图执行侧文件。背景按三关出三张变体,地面三关共用一张;场景级资产,与角色级 [art-prompts](art-prompts.md) 并列,两文件互不覆盖。
> **依据**:场景构成与图层归属 = [art-bible](art-bible.md) §6.1;三关方向 = art-bible §6.3;色板 = art-bible §4.1(战斗屏场景环境子板);尺寸 = `game/data/tables/battle_layout.tres`(背景读 `stage_h`;地面读 `ground_h`,该字段由地面层实装批新增并回填);命名与路径 = [asset-path-map](asset-path-map.md);后处理 = [art-pipeline-tools](art-pipeline-tools.md)。
> **边界**:只管三张背景变体与一张共用地面的提示词与验收;角色、格面、图标、小石台 `scene_stage_plaza.png` 与样张走 art-prompts 与脚本管线,不在本卡。
> **建立**:2026-10-31 · 状态:三关背景变体与共用地面已按本工单出图并入包(选定=dungeon v10c/underdark v10c/deep_underdark v10c/ground v12b,落点与读数见 art-bible §6.1「B 区舞台背景与共用地面的交付绑定」);出图通道由作者自定(参考候选可用内置 ImageGen,水印可留;入包终稿须干净)。
---
## 0. 使用流程(四步)
1. **出图**:按 §2 把提示词贴进生图渠道;每次出图先放 §1 的风格参考图。**先只出 dungeon 背景 1 张**,作者确认风格前缀后,再出 underdark / deep_underdark 两张变体与连续地面层。
2. **入库**:候选原图放 `artwork/candidates/scene_stage_bg/`(三关变体用 `dungeon_*` / `underdark_*` / `deep_underdark_*` 命名)与 `artwork/candidates/scene_stage_ground/`;`_登记.txt` 记用途与生成时间。
3. **后处理**:三张背景不去底(不透明全幅);连续地面层去底转透明(顶缘以上键控)+最近邻降采样+色板量化+底测试条;工具清单与已知边界见 [art-pipeline-tools](art-pipeline-tools.md)。
4. **转正**:终稿转入 `game/assets/art/scene/`;三张背景按 `board_type` 选图、连续地面层接线与布局表 `ground_h` 字段由实装批落码。
---
## 1. 风格参考图(每次出图都要带)
| 参考图 | 作用 |
|---|---|
| `game/assets/art/character/character_hero_idle.png` | 定像素密度、平涂质感、描边与暖色族 |
| `game/assets/art/board/terrain_floor_dungeon_1.png` | 定岩面/地面的笔触与色族;只借质感,不抄瓦片形状与拼块接缝 |
| `game/assets/art/scene/scene_stage_plaza.png` | 定近景层色族与顶缘处理;只借色与像素密度,不借石板铺装 |
**禁止**拿未经验证的自造图当风格参考——风格错了整批全废。元素参考可用 D&D 洞穴/地下城的岩层、裂缝、凹壁、碎石等常见构成(见 §3),但不把外部图当风格锚。
贴参考图时,在提示词末尾接这一句:
```
Style reference: the attached images are this game's own finished pixel art — match only their rendering style, the same pixel density, flat shading, hard edges and warm limited palette. They are style references only: do not copy their subjects, characters, gear, props, tile patterns or any specific rock or ground layout, and do not put any character into the scene.
```
---
## 2. 两层提示词
### 2.1 背景层(三关变体,按 `board_type` 选一张)
**画什么**:顶部空区(深色、无物);**纵深洞厅远景**——远层暗剪影(远处洞口/通道、层叠岩体、洞底轮廓)、中层主要细节层(岩台、岩柱、凹壁、裂缝、建筑剪影)、近层左右暗色岩框;三关母题按 §3.2。
**不画什么**:近景地面、地台、平台;贴墙感、单件圆柱/贴图直接贴顶、悬挂物;砖石墙、规则砌缝、方整石块、瓷砖/铺装;钟乳石、吊锥、洞顶结构;角色、生物、道具、火把/灯/发光物;文字/UI。
**三关变体**:
| 关卡 `board_type` | 资产 | 岩体/构图 | 色族 |
|---|---|---|---|
| dungeon | `scene_stage_bg_dungeon.png` | 深远洞厅:远层支洞/通道口;中层岩台+歪斜木栈道/脚手架+削尖木桩围栏 | 暖石棕(`#282220` / `#3E362C` / `#453723`) |
| underdark | `scene_stage_bg_underdark.png` | 深远洞厅:巨型石柱、细长石笋、蛛网、螺旋塔剪影、巨菇、裂谷石桥 | 冷蓝灰+少量紫罗兰灰(约 256° 域;避 300°–345°) |
| deep_underdark | `scene_stage_bg_deep_underdark.png` | 深远洞厅:石笋城郭、棱角石厅/扶壁、跨谷石桥、矿道口、烟囱塔群 | 灰黑主调+金属质矿脉 |
**出图画布**:三张同规格——终稿 2× = (2× 基准屏宽) × (2× `stage_h`),尺寸读 `battle_layout.tres`。平台不支持该宽高比时取可用最宽档、按中央 5:1 横带裁到目标宽高比(基准屏宽 / `stage_h`),禁横向拉伸。当前内置参考通道最宽 2560×1080,按中央带裁。
**共用底座正向提示词(三张共用)**:
```
Pixel art style. True pixel art game background layer drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about eight colors, retro 16-bit look, Aseprite style, all ages, bright cheerful fantasy cavern, not grim, flat side view, orthographic, no perspective distortion. A very wide horizontal composition, about 5 to 1 aspect ratio; if the canvas is taller than that band, paint the band across the middle and fill the area above and below with flat dark continuation #282220, keeping all detail inside the band. This is the FAR BACKGROUND of an underground cavern arena: the camera stands at the arena edge and looks across a deep cave chamber, so the view must read as a distant cavern interior with real depth, NOT as a flat wall close behind the characters. BACKGROUND LAYER ONLY: no ground, no floor, no platform, no characters, no creatures, no props in the near view, no torches, no lanterns, no light sources of any kind, no light shafts, no beams of light, no glow. TOP BAND (the top fifth of the band): completely empty flat solid darkness #282220, no texture, no ceiling, no stalactites, no hanging cones, no spikes, no objects, and nothing touches the top edge. DEPTH LAYERS, from far to near: FAR LAYER: the farthest depth, simple dark silhouettes of distant rock masses and two or three dark side tunnel openings; no continuous floor line, the far depth fades into darkness; these are the darkest and simplest shapes, their edges partly hidden behind the midground, and they never form isolated cutouts against the top band. MIDGROUND: the main readable layer across the middle of the band — several enormous rock masses and broad ledges at different heights that overlap and partly hide each other, irregularly spaced, not a row of evenly spaced pillars; a few tall natural rock pillars rise from behind the ledges; the central part of the frame opens into a deeper recess with one large dark tunnel mouth far behind it; shallow alcoves and recesses, cracks and fissures; this layer carries most of the detail, low in contrast; the rock reads as huge natural cave masses, not a built wall, with only a few faint, short, broken strata hints that fade out, never continuous wavy stripes. NEAR FRAME: at the left and right edges, two dark rock masses frame the view, slightly darker than the midground; no isolated cylinder, column or prop pasted against the top; no hanging shapes. CENTER: the central half of the band stays calm and low in detail so the characters and UI can read. The overlapping ledges, pillars and far silhouettes must create several depth planes, not one flat backdrop. BOTTOM BAND (the bottom third of the band): simple flat dark rock in #282220 fading into shadow, no floor, no ground plane, no continuous horizontal ground line, no stone platform, no pedestal, no baseboard, no props — this area will be covered by a separate ground layer and UI. WHOLE IMAGE: very dark and low in contrast, no bright highlights, no rim lights, no vignette, no complete solid shape that competes for attention; no dense horizontal wavy lines, no repeated banding, no zebra stripes, no wood-grain pattern.
```
**三关变体段(接在共用底座后)**:
dungeon:
```
Use warm limestone for the far and mid rock: dark warm brown-grey, from the darkest #282220 through #3E362C to the lighter #453723; broad calm rock faces and a few wide low ledges; only a couple of faint, broken strata hints, not bands across the wall. GOBLIN LAIR DETAILS, fully built into the rock, only in the midground and far layer, small and low contrast, partly hidden: one broken crude wooden walkway anchored into a distant ledge with its posts set into the rock, its middle hidden behind a rock outcrop; one short pointed stake fence embedded along the edge of a far ledge, partly behind a rock mass; a dark side tunnel mouth with a rough timber frame built into the rock; no isolated fence segments, no floating walkway pieces, no structures sitting on top of ledges like icons; never in the near frame, never at the top, never lit.
```
underdark:
```
Use cool desaturated blue-grey rock for the far and mid rock: the rock faces are dark cool blue-grey, not warm brown; the cracks are sharper and more vertical; in the far layer add simple dark silhouettes of a few giant natural rock pillars whose tops fade into the dark top band, slender stone spires rising from distant ledges, thin muted grey web strands between rocks, a few curved spiral tower silhouettes, huge mushroom silhouettes on far ledges, and a distant chasm crossed by a thin stone bridge; add only a few thin, muted blue-violet mineral seams around 256 degrees hue, never saturated purple or magenta; no glowing fungi, no faerie fire, no bricks, no regular masonry.
```
deep_underdark:
```
Use very dark neutral grey-black rock for the far and mid rock: the rock faces are grey-black and angular, with broken, step-like ledges; in the far layer add simple dark silhouettes of a city carved into a giant stalagmite, with angular stone halls, buttresses and stepped terraces, a few stone bridges spanning a rift, several mine entrances with dark timber frames, and a row of chimney and vent stacks; a few thin cool silver and copper-grey mineral veins run through the rock as matte metal, not glowing; no forge fire, no glow, no smoke plume, no bricks, no regular masonry.
```
**共用否定后缀(三张共用)**:
```
Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; black or heavy dark outlines; blur or compression artifacts; a vignette, dark corners, or any mood darkening; any saturated purple or magenta anywhere, any color with a hue between 300 and 345 degrees (the underdark's muted 256-degree blue-violet seams are allowed and must stay desaturated); brick patterns, rectangular cut stone blocks, regular masonry joints, mortar lines, tiled surfaces, paved surfaces, a near-view carved or dressed stone wall, a flat wall close behind the characters, a single isolated column or cylinder pasted against the top band, any element touching the top edge, isolated fence segments, floating walkway pieces, structures sitting on top of ledges like icons, a continuous horizontal floor line; dense horizontal wavy lines, repetitive wavy banding, zebra stripes, wood grain, layer-cake stripes, continuous parallel strata across the whole wall; stalactites, stalagmites used as hanging shapes, hanging cones, ceiling spikes, cave curtains; any ground, floor plane, stone platform, pedestal, stage floor, baseboard, path or paving in the lower band; any character, person, creature or animal; any light source of any kind, any torch, brazier, lantern, candle, crystal, glowing object, light shaft, god ray, beam of light, glow, bloom or bright patch; any props in the near view, bones, chains, rubble pile, furniture, murals, paintings, grass, moss, plants, trees, small foreground fungi or glowing fungi (the underdark's far-layer giant mushroom silhouettes are allowed and must stay dark and unlit); bright or saturated colors, any area lighter than the variant's lightest wall color; high contrast edges, rim lights, black outlines; text, letters, numbers, watermark, signature, logo; frame, border, panel, arrow or UI element.
```
**组合方式**:每张背景 = 共用底座 + 对应关卡变体段 + 共用否定后缀,拼起来即整段可贴。三张仅岩体形态与色族不同,构图规则一致。
**后处理**:三张均不去底;最近邻降采样到终稿尺寸,色板量化到 art-bible §4.1 战斗屏场景环境子板(三张共用该子板,新增冷/灰色按 §4.1 两道门入板),单张 ≤32 色;四道机检见 §4。
### 2.2 连续地面层(`scene_stage_ground`,三关共用,顶缘以上透明)
**画什么**:全宽近景地面——**干净压实泥土为主**,整片平整、中低明度;全幅只保留**零星 3–5 颗小石**,散点分布、不成排不成堆;顶缘为不规则泥土边+阴影缝,可带 2–3 颗小石;中央平缓留空;地面跨三关恒定。
**不画什么**:瓷砖、砖缝、规则铺装、石板块、踢脚线/直亮边;草、木地板;远景/洞壁/天空;角色、生物、道具、火把/灯;中央大石或碎石堆挡站位;满铺碎石、密集砂砾、成排/成堆碎石、亮色浅棕大片、泥点噪声、垃圾杂物。
**出图画布**:终稿 = 基准屏宽 × `ground_h`(`battle_layout.tres` 新增字段,地面层实装批回填);本件是极宽薄条,平台无对应宽高比时按横向可平铺纹理条出图(左右无缝,宽度取平台方档,高度取 `ground_h` 的整数倍),终稿裁/平铺到目标宽高比,禁横向拉伸。试片阶段可按构图比例先出,终稿尺寸读表。
**正向提示词**:
```
Pixel art style. True pixel art game layer drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about eight colors, retro 16-bit look, Aseprite style, all ages, bright cheerful fantasy cavern, not grim, flat side view, orthographic, no perspective distortion. A full-width horizontal texture strip of the NEAR GROUND of a bright underground cavern arena, seen from the front, drawn on a plain flat uniform light grey #F2F2F2 background for easy keying: the ground strip occupies the lower part of the canvas and extends down to the bottom edge, and everything above its top edge is clean empty #F2F2F2 with nothing drawn in it. The ground is mostly CLEAN packed bare dirt with large calm flat areas of nearly unbroken color, medium-dark warm brown, not light tan and not bright; across the whole strip there are only THREE OR FOUR small stones in total, scattered singly and far apart, plus two or three small stones along the top edge; no rows of pebbles, no clusters, no gravel patches, no piles; the middle half of the strip is smooth and clear so characters can stand there; the surface is only slightly uneven with two or three thin cracks; no dense speckles, no heavy noise, no scattered pebbles spread everywhere, no muddy patches, no dirt clumps, no debris. The TOP EDGE of the ground is an irregular edge of packed dirt with a thin dark shadow seam, with only two or three small stones, clearly marking the boundary between the ground and the far wall behind it; the top edge must NOT be a straight tile edge, NOT a clean bright rim, NOT a masonry joint, NOT a regular curb, NOT a continuous row of stones or equal-sized pebbles. Colors: packed earth body in warm tan #968264 with large darker areas of #7A6448 and #5E4C38, darker soil patches #453723 and #3E362C, only the few small stones in #C9AF83, dark cracks and shadow seam in #4C4038 and #282220; the overall ground is medium-dark, not light tan and not bright, no large light-colored areas; matte flat colors, no gloss. No regular paving, no tiles, no bricks, no stone slabs, no grass, no plants.
```
**负向提示词**:
```
Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; black or heavy dark outlines; blur or compression artifacts; a vignette, dark corners, or any mood darkening; any purple or magenta anywhere, any color with a hue between 300 and 345 degrees; tiles, tile joints, paving, rectangular stone slabs, bricks, masonry, mortar lines, a clean straight rim, a bright curb or a regular edge along the top of the ground; grass, moss, plants, trees, fungi; any character, person, creature or animal; any prop, tool, bone, chain, furniture, torch, brazier, lantern, crystal or glowing object; any far wall, cave wall, background scenery or sky above the ground, the area above the top edge must stay plain flat uniform light grey #F2F2F2; any large boulder or rubble pile in the middle of the ground, the middle must stay clear for standing; many stones, rows of pebbles, clusters of rubble, gravel patches, stone piles, a continuous row of stones along the top edge, light tan or bright ground, large light-colored areas, dense speckles, heavy noise, scattered pebbles spread everywhere, muddy patches, dirt clumps, debris, garbage, a messy or dirty ground surface; any complete solid shape that competes for attention; bright or saturated colors, anything lighter than #C9AF83 inside the ground itself (the flat #F2F2F2 keying area above the top edge is required and allowed); high contrast edges, rim lights, black outlines; text, letters, numbers, watermark, signature, logo; frame, border, panel, arrow or UI element.
```
**后处理**:去底(`#F2F2F2` 键控,顶缘以上转透明)+ 最近邻降采样到终稿尺寸 + 色板量化;地面主体色落 art-bible §4.1 世界中层色族,单张 ≤32 色;四道机检见 §4。
---
## 3. 纵深结构与三关母题(2026-10-31 作者认可)
### 3.1 纵深结构(三关共用)
| 层次 | 放什么 | 规则 |
|---|---|---|
| 远层 | 远处洞口/通道、层叠岩体、洞底轮廓、远景建筑剪影 | 压到最深,细节最少;边缘被中层遮住,不孤立、不贴顶 |
| 中层 | 岩台、岩柱、凹壁、裂缝、主要建筑剪影 | 细节主要放这里;低对比 |
| 近层 | 左右暗色岩体作画框 | 不挡中央;禁单独圆柱/贴图贴顶 |
| 顶部/底部 | 顶部空区、底部暗部 | 顶部不挂钟乳石/吊锥;底部留给地面层 |
| 中央 | 角色站位区 | 1/2–2/3 宽保持低细节、无大障碍 |
### 3.2 三关母题(推荐项启用,可选项关闭)
| 关卡 | 推荐项(本版启用) | 可选项(本版关闭) |
|---|---|---|
| dungeon 地精 | 远处支洞/通道口、木质栈道/脚手架、削尖木桩/围栏残段 | 骨堆/头骨图腾、蘑菇丛/苔藓、暗河/细水流、丢弃箱桶/破布 |
| underdark 卓尔 | 巨型天然石柱、细长石笋群/尖塔、蛛网/蛛丝、弧形塔楼/螺旋尖塔剪影、巨型蘑菇剪影、裂谷+石桥、紫罗兰灰矿脉 | 雕刻拱门/门廊、蛛形雕刻 |
| deep_underdark 灰矮人 | 挖空石笋的城郭剪影、棱角石厅/扶壁/台阶、跨裂谷石桥、矿道口/木框矿井、烟囱/通风塔群、铜灰/银灰矿脉 | 铁箍/铁门/锁链、暗湖边缘、矮人面雕像 |
### 3.3 通用排除
钟乳石/吊锥/悬挂物、砖石墙/规则砌缝/瓷砖/铺装、贴墙感、单件圆柱贴顶、光柱/光带/光源/发光物、火把/灯、角色/生物、文字/UI、紫/洋红。
**近景地面**:独立连续地面层,画法与验收见 §2.2,不并入背景。
---
## 4. 验收(机检 + 目检)
**三张背景变体 `scene_stage_bg_<board_type>`**
- 每张画布 = (基准屏宽, `stage_h`) 逐位相符(终稿);文件名带 `_dungeon` / `_underdark` / `_deep_underdark` 后缀。
- 顶部空区无钟乳石/吊锥/悬挂物,且无任何元素触碰画面上缘;远景为天然岩体,无砖石/规则砌缝/瓷砖/方整石块。
- 纵深分层:远/中/近三层可辨;远层剪影被中层部分遮挡、不孤立、不贴顶;无贴墙感、无单件圆柱/贴图直接贴顶;无连续地平线/地面;无孤立栅栏/栈道图标。
- 背景不含近景地面、地台、平台;底带只留暗部(结算期被明细带压住)。
- 无光柱/光带/光束/光晕,无光源实体(火把/灯/发光物);明暗只由岩体层理与暗部承载。
- 明度:全图取样点 L ∈ [0.014,0.060](世界底层下沿带,art-bible §2)。
- 无 300°–345° 色相;无角色/生物/道具/文字/UI;无完整凸形焦点(立绘 > 背景)。
- 背景不引用 `wall_ambient` tint;三张按 `board_type` 选图。
- 目检:三关色族与母题——dungeon 暖石棕+支洞/木栈道/木桩围栏;underdark 冷蓝灰+巨柱/石笋/蛛网/螺旋塔/巨菇/石桥;deep_underdark 灰黑+石笋城郭/棱角石厅/石桥/矿道口/烟囱塔;可选项不出现;三张可辨为不同洞窟,不是同一张图换色。
**共用连续地面层 `scene_stage_ground`**
- 画布 = (基准屏宽, `ground_h`)(终稿;`ground_h` 读布局表);文件名无 board_type 后缀。
- 地面为干净压实泥土为主,全幅只零星几颗小石、散点分布;无瓷砖/砖缝/规则铺装/石板块/草。
- 顶缘 = 不规则泥土边/阴影缝,只带 2–3 颗小石;不是直线亮边/踢脚线,也不是一排石子;顶缘以上键控后透明、无残留底色。
- 中央 1/2–2/3 宽平缓、无大石阻挡;无成排/成堆碎石、满铺砂砾、泥点噪声、垃圾杂物。
- 地面主体色 L ∈ [0.22,0.45] 的中低段(世界中层,art-bible §2;裂缝/阴影不按明度带判定;避免浅棕/亮面)。
- 三关共用、不吃 `wall_ambient`;无角色/生物/道具/文字/UI。
- 目检:地面读作泥土/碎石,不是瓷砖或石板铺装。
**参考与入包**:参考候选可带水印;入包终稿须无水印;四件终稿均按 §0 后处理与 `art-pipeline-tools` 的判据执行。
---
## 5. 待裁与待办(不阻塞提示词成文)
1. **三张背景按 `board_type` 选图**——游戏代码改动,走需求卡片+迭代;背景 `wall_ambient` tint 退役,棋盘/世界的该槽保留。
2. **`ground_h` 字段**——布局表新增字段,地面层实装批回填;本卡试片按构图比例,终稿读表。
3. **冷/灰色入板**——underdark / deep_underdark 需要的冷蓝灰、灰黑与矿脉色按 art-bible §4.1 战斗屏场景环境子板两道门入板;三张背景共用该子板 ≤32 色。
4. **地面层出图工艺**——极宽薄条;默认横向可平铺纹理条(左右无缝);若作者要整幅不拼接,另议。
5. **小石台 `scene_stage_plaza.png`**——先保留;是否退役归实装批/后续裁决。
6. **四柱光柱程序层退役**——光柱不迁入背景;背景资产入包后该程序层退役(代码批)。
7. **风格前缀**——首张 dungeon 背景试片后由作者确认,确认后才出另外两张与地面层。
8. **可选要素池**——按 §3.2 可选项本版关闭;要加走本卡修订。
---
## 6. 不在本卡
角色与敌种立绘、棋盘格面/墙面纹理、UI 图标、护甲图标、小石台 `scene_stage_plaza.png`、整屏样张与游戏代码接线;这些各归 art-prompts、脚本管线与实装批。
+59 -31
View File
@@ -1,9 +1,9 @@
# 营地场景 · 分层生图提示词卡 v1.0 # 营地场景 · 分层生图提示词卡 v1.4
> **性质**:营地场景图(1280×800,一张可点击场景图 + 八个物件层)的出图执行侧文件。**场景级资产,与角色级 [art-prompts](art-prompts.md) 并列,两文件互不覆盖**。 > **性质**:营地场景图(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 为准。 > **依据**:色彩与氛围 = `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 为准。
> **边界**:只管营地场景图的分层出图。角色、图标、格面走 art-prompts.md 与脚本管线,不需要提示词。 > **边界**:只管营地场景图的分层出图。角色、图标、格面走 art-prompts.md 与脚本管线,不需要提示词。
> **建立**:2026-10-25 · 状态:九条提示词成文,出图未开始(出图通道由作者自定) > **建立**:2026-10-25 · 状态:十条提示词成文(背景层 1 + 围墙层 1 + 八个物件层),出图通道由作者自定
--- ---
@@ -11,10 +11,10 @@
1. **出图**:按下面各条把提示词贴进生图渠道。出图前先放**风格参考图**(见 §1)。 1. **出图**:按下面各条把提示词贴进生图渠道。出图前先放**风格参考图**(见 §1)。
2. **入库**:候选原图放 `artwork/candidates/<资产名>/`,文件名 `v1.png / v2.png / v3.png`。 2. **入库**:候选原图放 `artwork/candidates/<资产名>/`,文件名 `v1.png / v2.png / v3.png`。
3. **后处理**:脚本管线代跑——去底转透明、降采样到终稿尺寸(整数倍)、色板量化、四联底测试条。 3. **后处理**:脚本管线代跑——去底转透明、降采样到终稿尺寸(整数倍)、色板量化、四联底测试条。**背景层不去底(全幅不透明)**;围墙层与八个物件层去底转透明。后处理工具清单与用法见 [art-pipeline-tools](art-pipeline-tools.md)。
4. **转正**:终稿转入切片资产目录,按坐标表接线。 4. **转正**:终稿转入切片资产目录,按坐标表接线。
**出图顺序(先锁后产,不能跳)**:概念脚本 → **本卡提示词** → 贴风格参考图出背景层 → 作者确认风格前缀 → 才扩产八件。 **出图顺序(先锁后产,不能跳)**:概念脚本 → **本卡提示词** → 贴风格参考图**先出背景层 `scene_camp_bg` 1 张** → 作者确认风格前缀 → 才扩产围墙层 `scene_camp_wall` 与八个物件层(共 9 张)。
--- ---
@@ -37,24 +37,40 @@ Style reference: the attached images are this game's own finished pixel art and
--- ---
## 2. 背景层(`scene_camp_bg`,不可点,不透明全幅) ## 2. 背景层与围墙层(两张,不可点)
围墙从背景层拆出、独立成层:围墙层可单独锁死桩高、门洞做成透明缺口透出背景层的小路;背景层不混入暖木色、更易守住冷色占比判据(判据见 §4)。两层都不可点;按乙工序门**先只出背景层 1 张**供作者确认风格前缀,确认后才扩产围墙层与八个物件层。
### 2.1 背景层(`scene_camp_bg`,不可点,不透明全幅)
**出图画布**:2560×1600(终稿 1280×800 的 2 倍,保证整数倍降采样) **出图画布**:2560×1600(终稿 1280×800 的 2 倍,保证整数倍降采样)
**画什么**:天空、远山、地面、木围墙、门柱与门洞 **画什么**:天空、远山、整片地面、门洞位置一条通向远处的小路
**不画什么**:七个物件、门扇、角色、接地阴影——那些是上面的独立图层 **不画什么**:木围墙、门柱、门扇、七个物件、角色、接地阴影——围墙与门柱是 §2.2 的独立层,门洞是它的透明缺口;小路在本层画好、透出来即可
``` ```
Pixel art style. True pixel art game background layer drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about 24 colors, retro 16-bit look, Aseprite style, all ages, flat side view, orthographic, no perspective distortion. Wide horizontal composition, 16:10 aspect ratio. This is the BACKGROUND LAYER ONLY of a small adventurer camp: sky, distant hills, ground and the wooden palisade wall around it. It must contain no camp objects, no gate leaves, no characters and no drop shadows, because all of those are drawn as separate transparent layers on top of this one. COLOR SCRIPT — cool recedes, warm advances: the sky, the hills, the ground and the wall are all COOL, pale, desaturated and low in contrast; there is no warm accent anywhere in this layer, warm color belongs only to the separate object layers drawn on top. ATMOSPHERIC DEPTH: every plane that is farther away is lighter, closer to the value of the sky, cooler, lower in contrast and simpler in detail, while the nearest plane is the darkest and highest in contrast. SKY (top of frame): the lightest thing in the picture, flat pale cool off-white with a faint blue-grey tint, no yellow or cream tone, no sun anywhere; the very top strip of the frame, about one sixteenth of the frame height, stays completely calm and empty because a HUD bar is drawn over it; at most one or two extremely simple flat low-contrast clouds. FAR DISTANCE: two or three layers of low rolling hill silhouettes in pale cool blue-grey, each farther layer lighter, cooler and lower in contrast than the one in front, fading towards the value of the sky, no trees, no individual foliage; the foot of the hills is cut off and hidden behind the wall, so there is no visible horizon line and no distant ground band between the hills and the wall. MIDDLE GROUND: the base line where the logs meet the ground sits at two thirds of the frame height measured from the bottom edge; at that height runs a wooden palisade wall across the whole width of the frame, built from closely spaced vertical logs standing side by side with no gaps between them, every log cut to a sharp point at the top, a solid barrier that completely blocks the view of the outside — no horizontal rails, no see-through gaps, no ranch-style post-and-rail fence; the wall is painted in a muted, greyed wood brown. In the middle of the wall one wide gap holds two thick gate posts slightly taller than the wall, and between them an opening showing a dirt path running away into the distance; the gate opening itself stays empty, because the two gate leaves are a separate layer. FOREGROUND GROUND: the darkest area of the picture, dry packed bare earth in a neutral desaturated grey-brown, never golden, amber or warm yellow, hard flat color blocks, hard edges, no gradient, no grass, no grass tufts, no weeds and no plants of any kind; the ground meets the foot of the wall cleanly with no shadow band and no gradient. Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; black or heavy dark outlines; drop shadows, contact shadows or any shadow band; blur or compression artifacts; a vignette, dark corners, or any mood darkening; night, dusk, moon, stars, sunset or sunrise light; a sun or any light source drawn in the sky; a visible horizon line, a distant ground band or any distant grass strip below the hills; trees, forests or foliage in the distance; grass, grass tufts, weeds or plants in the foreground; a ranch-style post-and-rail fence, a see-through fence, flat-topped posts or horizontal rails; any warm, golden, amber or yellow cast on the sky, on the hills or on the ground; any camp object, furniture, crate, barrel, fire, tent or tool; any gate leaf, door panel or hinge; any purple or magenta anywhere; any text, letters, numbers, watermark, signature or logo; any frame, border, panel, arrow or UI element; any characters, people, creatures or animals. Pixel art style. True pixel art game background layer drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about 24 colors, retro 16-bit look, Aseprite style, all ages, flat side view, orthographic, no perspective distortion. Wide horizontal composition, 16:10 aspect ratio. This is the FAR BACKGROUND LAYER ONLY of a small adventurer camp: sky, distant hills and the whole ground. It must contain no palisade wall, no gate posts, no camp objects, no gate leaves, no characters and no drop shadows, because all of those are drawn as separate transparent layers on top of this one. COLOR SCRIPT — cool recedes, warm advances: the sky, the hills and the ground are all COOL, pale, desaturated and low in contrast; there is no warm accent anywhere in this layer, warm color belongs only to the separate object layers drawn on top. ATMOSPHERIC DEPTH: every plane that is farther away is lighter, closer to the value of the sky, cooler, lower in contrast and simpler in detail, while the nearest plane is the darkest and highest in contrast. SKY (top of frame): the lightest thing in the picture, flat pale cool off-white with a faint blue-grey tint, no yellow or cream tone, no sun anywhere; the very top strip of the frame, about one sixteenth of the frame height, stays completely calm and empty, reserved as clear space with nothing in it; at most one or two extremely simple flat low-contrast clouds. FAR DISTANCE: two or three layers of low rolling hill silhouettes in pale cool blue-grey, each farther layer lighter, cooler and lower in contrast than the one in front, fading towards the value of the sky, no trees, no individual foliage. HILLS TO GROUND: the hills fade gradually and smoothly down into the ground, with NO visible horizon line and NO distant ground band — the transition must not read as a separate horizontal stripe of a different color. GATE PATH: in the middle of the frame, between x=1040 and x=1520, one clearly visible dirt path runs from the ground at two thirds of the frame height measured from the bottom edge away into the distance between the hills, narrowing as it recedes, painted in a muted desaturated grey-brown earth tone, never golden or warm yellow; this path shows through the empty gate opening of the wall layer above. FOREGROUND GROUND: the darkest area of the picture, dry packed bare earth in a neutral desaturated grey-brown, never golden, amber or warm yellow, hard flat color blocks, hard edges, no gradient, no grass, no grass tufts, no weeds and no plants of any kind. The foreground ground is completely empty apart from the dirt path: no objects, no icons, no badges, no markers, no decorations of any kind. Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; black or heavy dark outlines; drop shadows, contact shadows or any shadow band; blur or compression artifacts; a vignette, dark corners, or any mood darkening; night, dusk, moon, stars, sunset or sunrise light; a sun or any light source drawn in the sky; a visible horizon line, a separate horizontal distant ground band or any distant grass strip below the hills; trees, forests or foliage in the distance; grass, grass tufts, weeds or plants in the foreground; a palisade wall, wooden fence, fence posts, gate posts or any wall structure; any warm, golden, amber or yellow cast on the sky, on the hills or on the ground; any camp object, furniture, crate, barrel, fire, tent or tool; any gate leaf, door panel or hinge; any purple or magenta anywhere; any text, letters, numbers, watermark, signature or logo; any frame, border, panel, arrow or UI element; any icon, badge, item tile, emblem, sign, marker or symbol anywhere in the frame; any characters, people, creatures or animals.
``` ```
### 出图画布上的坐标(终稿 ×2) ### 2.2 围墙层(`scene_camp_wall`,不可点,透明底)
**出图画布**:2560×1600(与背景层同画布,门洞 x 坐标直接对齐);墙横贯画在出图 y480–620 这条**窄带**内,其余为纯色底 `#F2F2F2`。落盘由 S4 后处理裁成终稿外接矩形 `(0, 240, 1280, 70)` / 出图 `(0, 480, 2560, 140)` 的细条,**不做全幅 1280×800 落盘**(全幅约 3.9 MB 显存,细条约 0.35 MB)。
**画什么**:横贯全宽的密排竖木桩墙(左右两段)+ 两根略高的门柱,门洞处完全透明
**不画什么**:天空、远山、地面、小路、门扇、七个物件、角色;门洞内不画任何东西,透出背景层的小路
```
Pixel art style. True pixel art game layer drawn with chunky square pixels, strict pixel grid alignment, hard edges, no anti-aliasing, no gradients, flat colors only, a limited palette of about 24 colors, retro 16-bit look, Aseprite style, all ages, flat side view, orthographic, no perspective distortion. Wide horizontal composition, 16:10 aspect ratio. This is the PALISADE WALL LAYER ONLY of a small adventurer camp, drawn on a plain flat uniform light grey #F2F2F2 background for easy keying. A wooden palisade wall runs straight across the full width of the frame, seen from the front, built from closely spaced vertical logs standing side by side with no gaps between them, every log cut to a sharp point at the top, a solid barrier with no horizontal rails, no see-through gaps, no ranch-style post-and-rail fence. The wall is SHORT and LOW: including the pointed tips the whole band occupies only about one eighteenth of the total frame height, a thin horizontal strip, NOT a tall barrier; the logs stand only about one eighteenth of the total frame height above their base line. In the middle of the wall one wide gap holds two thick gate posts slightly taller than the wall, and the entire opening between them is completely EMPTY — plain flat uniform light grey #F2F2F2 showing through, no logs, no path, no ground, no scenery inside the opening, because the dirt path and the hills belong to the background layer below and the gate leaves are a separate object layer. The wall is painted in a muted greyed wood brown, with a thin warm cream outline around the whole silhouette, flat shading. Do not add any of the following: realistic, photorealistic or 3D rendering; painterly brushwork, smooth anti-aliased lines, gradient or dithered shading, glossy highlights; black or heavy dark outlines; drop shadows or contact shadows; blur or compression artifacts; a vignette or dark corners; any sky, hills, ground, grass, dirt path or scenery of any kind; any gate leaf, door panel or hinge; any camp object, furniture, crate, barrel, fire, tent or tool; any characters, people, creatures or animals; any purple or magenta anywhere; any text, letters, numbers, watermark, signature or logo; any frame, border, panel, arrow or UI element.
```
### 2.3 出图画布上的坐标(终稿 ×2)
| 元素 | 终稿 1280×800 | 出图画布 2560×1600 | | 元素 | 终稿 1280×800 | 出图画布 2560×1600 |
|---|---|---| |---|---|---|
| 顶栏 HUD 保留带 | y ≤ 48 | **y ≤ 96**(保持空旷) | | 顶栏 HUD 保留带 | y ≤ 48 | **y ≤ 96**(保持空旷) |
| 围墙带 | y 267–310 | **y 534–620** | | 背景层 `scene_camp_bg` | 全幅 1280×800 | 全幅 2560×1600 |
| 门洞(必须留空) | x 520–760 | **x 1040–1520** | | 围墙带(wall 层桩身) | y 267–310(**高 43px**) | y 534–620(**高 86px**) |
| 门柱 | x 500–520 / 760–780 | **x 1000–1040 / 1520–1560** | | 门柱(wall 层,略高) | y 240–310;x 500–520 / 760–780 | y 480–620;x 1000–1040 / 1520–1560 |
| 门洞(wall 层透明缺口) | x 520–760 | x 1040–1520 |
| 门洞内小路(画在 bg 层) | x 520–760,自 y310 向上收窄至远山 | x 1040–1520,自 y620 向上收窄至远山 |
--- ---
@@ -62,7 +78,7 @@ Pixel art style. True pixel art game background layer drawn with chunky square p
### 组装方式(不要逐条重复前缀) ### 组装方式(不要逐条重复前缀)
每件提示词 = **风格前缀** + **本件正文** + **否定后缀**。前缀与后缀在**本节单点定义**,九张共用,避免同一段话九处维护。 每件提示词 = **风格前缀** + **本件正文** + **否定后缀**。前缀与后缀在**本节单点定义**,**八件共用**,避免同一段话八处维护。背景层与围墙层用各自的完整提示词(§2.1/§2.2),不用本节的物件前缀。
**风格前缀**(八件共用,照抄): **风格前缀**(八件共用,照抄):
@@ -78,16 +94,18 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
### 逐件正文(只替换这一段) ### 逐件正文(只替换这一段)
| 件 | 层 | 终稿矩形 | 出图画布 | 正文 | | 件 | z 序 | 终稿矩形 | 出图画布 | 正文 |
|---|---|---|---|---| |---|---|---|---|---|
| 布告板 | L1 | 130×140 | 260×280 | §3.1 | | 布告板 | 2 | 130×140 | 260×280 | §3.1 |
| 门扇 | L2 | 240×220 | 480×440 | §3.2 | | 门扇 | 3 | 240×220 | 480×440 | §3.2 |
| 木桩 | L3 | 120×180 | 240×360 | §3.3 | | 木桩 | 4 | 120×180 | 240×360 | §3.3 |
| 锻炉·铁砧 | L4 | 240×240 | 480×480 | §3.4 | | 锻炉·铁砧 | 4 | 240×240 | 480×480 | §3.4 |
| 帐篷 | L5(不可点) | 300×170 | 600×340 | §3.5 | | 帐篷 | 4(不可点) | 300×170 | 600×340 | §3.5 |
| 宝箱 | L6 | 130×90 | 260×180 | §3.6 | | 宝箱 | 5 | 130×90 | 260×180 | §3.6 |
| 笔记本 | L7 | 100×80 | 200×160 | §3.7 | | 笔记本 | 5 | 100×80 | 200×160 | §3.7 |
| 篝火 | L8 | 160×160 | 320×320 | §3.8 | | 篝火 | 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 布告板(→ 图鉴)** **3.1 布告板(→ 图鉴)**
``` ```
@@ -133,10 +151,20 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
## 4. 验收(机检 + 目检) ## 4. 验收(机检 + 目检)
**背景层** **背景层 `scene_camp_bg`**
- 黄度 =(R+G)/2−B 分区取样:天空 ≤20、远山 ≤20、地面 ≤35 - 黄度 =(R+G)/2−B 分区取样:天空 ≤20、远山 ≤20、地面 ≤35
- 冷色像素占比 ≥55%;本层不应出现任何暖色高饱和像素 - 冷色像素占比 ≥55%;本层不应出现任何暖色高饱和像素
- 目检:顶部约 1/16 完全空旷;无地平线;围墙密排尖桩、桩顶削尖、不透缝;门洞空着没有门扇 - 目检:顶部约 1/16 完全空旷;**无地平线、无远景地面带**(远山自然渐隐过渡到地面);不含围墙、门柱、门扇、物件、角色
- 目检/机检:门洞 x1040–1520 处**有一条清晰可辨、向远处收窄的土路**(非纯色块),透出「出去即冒险」
- 机检/目检:全幅**无任何独立物件/图标/徽章/标记**(前景地面除小路外全空);参考做法=中下方块区独立色数应≈邻区(旧口径 v1 该区仅 179 色)
**围墙层 `scene_camp_wall`**
- 透明底 `#F2F2F2` 可去底、奶油描边闭合
- **桩身高度 = 出图 y534–620(终稿 43px);门柱略高 y480–620。桩身超出围墙带下沿 y620 达 5px 以上即判红**
- 目检:密排竖木桩、桩顶削尖、不透缝、无横木;门洞 x1040–1520 **完全透明**(透出 bg 小路)、无门扇
- 单张色数 ≤32(量化后)、缩放为整数倍无拉伸痕迹
> **量化与缩放实况注记(2026-11-01 迭代 47 卡④ 落地)**:围墙层与八件已按营地主色板 **32 色重量化回写**(art-bible §4.1 子板行——违例对 0);落位缩放实况=源 2× 档经后处理换算为锁定矩形 contain(×0.1934–0.4404 非整数倍),「整数倍无拉伸痕迹」判据的机检面以落位实测尺寸与目检兜底执行。
**八件** **八件**
- 单件居中、四周留边、整件完整不出画 - 单件居中、四周留边、整件完整不出画
@@ -147,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 待重定,重定后按比例重出 1. **帐篷尺寸**:300×170 追认现值(各素材第一张样本稿已过审为终稿;重定即按比例重出、不裁)
2. **门扇高宽**:现 240×220,围墙带只有 43px 高,比例待裁 2. **门扇高宽**:240×220 追认现值,坐标按门洞 x520–760 对齐(§2.3);围墙带桩身高 43px、门扇叠在门洞上的叠放关系随此锁定
3. **布告板位置**:现 y330 悬空在围墙下方,建议 `(845, 268, 130, 140)` 挂回围墙带 3. **布告板位置**:采纳建议值 `(845, 268, 130, 140)` 挂回围墙带(y330 悬空案弃);终值随坐标表(卡④ `camp_scene_layout.tres`)落盘
4. **描边颜色追认**:本卡按奶油色(去底技术前提);若改参照物的深灰褐,需换去底方案 4. **描边颜色**:奶油色追认——去底脚本「奶油描边膨胀 3px 当屏障」的技术前提随现值锁定(§1 注记同源);改深灰褐须换去底方案全批重出,不裁
5. **营地环境子板色值**:art-bible §4.1 为数值单点,色值待作者拍板,本卡不复述 5. **营地环境子板色值**:不前置拍板——T1 量化场景实际用色后按 art-bible §4.1 两道门登记(语义检查+RGB 三通道差 ≥8/255),本卡仍不复述色值
+2 -2
View File
@@ -42,7 +42,7 @@
- **兜帽披肩**:兜帽**不戴在头上**,取下披于肩上——露出棕色短发与整脸(表情可读),蓝色兜帽披在肩头仍是最近的"己方"识别点;三态同款披肩。 - **兜帽披肩**:兜帽**不戴在头上**,取下披于肩上——露出棕色短发与整脸(表情可读),蓝色兜帽披在肩头仍是最近的"己方"识别点;三态同款披肩。
- **圆头挖掘铲(武器)**:持铲不持剑——**铲 = 本作核心动词"挖安全格"的角色化**,挖掘与攻击共用同一动作语义(攻击表现即挥铲,后续攻击类表现优先围绕铲展开)。画法:圆头铲刃 + 木柄(木柄走木色族 `#8A5A34`,铲刃浅铁灰、不做冷灰重甲质感);**待机态 = 铲插立在地上、一只手扶在铲柄顶端**;攻击/受击态仍为手持。刀光/铲击光效/弹道不进精灵图(归战斗演出另立资产)。 - **圆头挖掘铲(武器)**:持铲不持剑——**铲 = 本作核心动词"挖安全格"的角色化**,挖掘与攻击共用同一动作语义(攻击表现即挥铲,后续攻击类表现优先围绕铲展开)。画法:圆头铲刃 + 木柄(木柄走木色族 `#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)验证背负后剪影仍窄、仍可辨、不超格宽。 - **冒险背包与背包旗(身份件)**:背负**结构式冒险背包**——翻盖+皮扣带的帆布背包(暖卡其布面,皮革件走木色族),**宽 ≈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 层,不进精灵图)。 - **三态**:① 待机 = 窄、直立、对称(§5.1 小人原型,含背包、旗与提灯记账);② 攻击 = 允许破对称,整体仍窄、不超格宽;③ 受击 = 无血红、无伤害数字(伤害数字归 HUD 层,不进精灵图)。
- **随身件 = 秘宝形象候选池**:铲、旗、背包、提灯四件随身件是未来秘宝设计的**形象候选池**——道具实装可借用主角身上已有的同款(彩蛋闭环:玩家在道具栏认出主角身上的物件);具体道具语义/数值归秘宝 GDD,人设侧义务 = 四件画得出来、认得出。 - **随身件 = 秘宝形象候选池**:铲、旗、背包、提灯四件随身件是未来秘宝设计的**形象候选池**——道具实装可借用主角身上已有的同款(彩蛋闭环:玩家在道具栏认出主角身上的物件);具体道具语义/数值归秘宝 GDD,人设侧义务 = 四件画得出来、认得出。
@@ -67,7 +67,7 @@
- **主色域 = 物种本色**,不受圣经 §4.2 语义色禁则约束(该禁则管格面与 UI 的逻辑色,角色身体主色豁免);**两例外**:禁入带 300°–345° 全角色禁入(仅 drider/duergar_king 两 Boss 独占)、蓝族禁入敌方主色(§5.4 同款)。 - **主色域 = 物种本色**,不受圣经 §4.2 语义色禁则约束(该禁则管格面与 UI 的逻辑色,角色身体主色豁免);**两例外**:禁入带 300°–345° 全角色禁入(仅 drider/duergar_king 两 Boss 独占)、蓝族禁入敌方主色(§5.4 同款)。
- **L2 熊地精 = bugbear 复用**(第二关三行,显示名熊地精系,非独立键):同剪影**换色板不换形**(色板替换走脚本管线——原则三"先换色板"的角色侧应用);变体主色避小人蓝族与禁入带。 - **L2 熊地精 = bugbear 复用**(第二关三行,显示名熊地精系,非独立键):同剪影**换色板不换形**(色板替换走脚本管线——原则三"先换色板"的角色侧应用);变体主色避小人蓝族与禁入带。
- 每敌种出 2–3 版候选(清单 §2.2);受击反馈/死亡表现/顺位标记三类呈现下限见圣经 §4.6.4 资产需求行。 - 每敌种出 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)终审复验灰度剪影可辨性。 - **钩爪恐魔「宽≥高」豁免登记(待样张终审)**:定档源 58 高窄条束状剪影(长臂圆钩),宽高比 <1,与 §5.1 普通怪「近圆墩形宽≥高」冲突——按体量=生物天性(§5.2)登记豁免:以长臂束状轮廓为识别锚,不强行压成墩形;样张(圣经 §1.4)终审复验灰度剪影可辨性。
## 5.6 意图准备动画三档(规格承载节) ## 5.6 意图准备动画三档(规格承载节)
+32 -26
View File
@@ -2,8 +2,8 @@
## 文档状态 ## 文档状态
- 版本:1.26 - 版本:1.28
- 最近更新:2026-10-30(迭代 42 反哺点落文——ShopSession 行对外暴露格按委托壳拆除后实测直连面重写:完整直连方法清单+set_shop_pool 调用面改述;沿革查 git) - 最近更新: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 锁定) - 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调) - 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准) - 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准)
@@ -85,11 +85,11 @@
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API | | 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|---|---|---|---|---| |---|---|---|---|---|
| **ConfigDB**(autoload,D2) | 全部玩法数值表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进三张域容器 Resource(`config_domain_combat/run/crossrun.gd`——已建件(迭代 41),类型化成员 7/7/5);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 9 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW | | **ConfigDB**(autoload,D2) | 全部玩法数值表与表现层布局配置表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进四张域容器 Resource(`config_domain_combat/run/crossrun/presentation.gd`——前三件迭代 41 已建件,第四件表现层布局域 2026-10-30 建件,类型化成员 7/7/5/1);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 10 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10/类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW | | **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10/类变量 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 | | **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(无) | 无——纯逻辑 | | **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) | 无 | | **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) | | **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 持有) | 无(零随机全确定) | | **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 持有) | 无(零随机全确定) |
@@ -110,7 +110,7 @@
| **HeroLedger**(`core/hero_ledger.gd`,冒险域主角实体账,已实装(2026-10-27 迭代 39 拆件批)) | 冒险生命周期主角数值与获得记录唯一所有者:生命与 GP 钱包(gain/spend/clear 单写原语,钱包事件发射点归件)、锻造强化快照、调试百分比、攻击/甲类型轴快照、特性持有集(活引用注入——关内兑付当关生效)、本局钥石增量与逐杀明细、真视使用记录;获得记录三层结构=获得记录列表(三选一件/商店购买/战末奖励各一条)→属性增益明细(带来源指回)→四轴汇总(由明细推导);生效上限与回血封顶公式单点归件 | 构造注入;单写原语族+只读投影;断点自交片段(账本键+获得日记键) | ConfigDB(表值读取);由 AdventureRunner 冒险开始创建、注入 CurioStop/level_runner/ShopSession/战斗 encounter | 无(RefCounted 纯逻辑) | | **HeroLedger**(`core/hero_ledger.gd`,冒险域主角实体账,已实装(2026-10-27 迭代 39 拆件批)) | 冒险生命周期主角数值与获得记录唯一所有者:生命与 GP 钱包(gain/spend/clear 单写原语,钱包事件发射点归件)、锻造强化快照、调试百分比、攻击/甲类型轴快照、特性持有集(活引用注入——关内兑付当关生效)、本局钥石增量与逐杀明细、真视使用记录;获得记录三层结构=获得记录列表(三选一件/商店购买/战末奖励各一条)→属性增益明细(带来源指回)→四轴汇总(由明细推导);生效上限与回血封顶公式单点归件 | 构造注入;单写原语族+只读投影;断点自交片段(账本键+获得日记键) | ConfigDB(表值读取);由 AdventureRunner 冒险开始创建、注入 CurioStop/level_runner/ShopSession/战斗 encounter | 无(RefCounted 纯逻辑) |
| **AttuneLoadout**(`core/attune_loadout.gd`,同调配装件,已实装(2026-10-27 迭代 39 拆件批)) | 装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期);同调充能的物化行为唯一事实源——种行/补行改写时同步(RunLedger 充能字典轨与差额对账函数退役);旧档 aux_items 同调行迁移归件 | 构造注入;充能查询/消耗/按周期重置原语;断点自交片段(attunement_charges 键,键名与策略不变) | ConfigDB(秘宝行集);TreasureItems(同调行投放面);注入战斗使用路径 | 无 | | **AttuneLoadout**(`core/attune_loadout.gd`,同调配装件,已实装(2026-10-27 迭代 39 拆件批)) | 装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期);同调充能的物化行为唯一事实源——种行/补行改写时同步(RunLedger 充能字典轨与差额对账函数退役);旧档 aux_items 同调行迁移归件 | 构造注入;充能查询/消耗/按周期重置原语;断点自交片段(attunement_charges 键,键名与策略不变) | ConfigDB(秘宝行集);TreasureItems(同调行投放面);注入战斗使用路径 | 无 |
| **CurioStop**(`core/curio_stop.gd`,三选一停点件,已实装(2026-10-27 迭代 39 拆件批)) | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途)、刷新三源账(刷新件 X/营地池 Y/刷新券存量/付费刷新计数)、兑付方法族(pick/refresh/redeem/skip)、凭证出箱(RAP/时沙待注入);三选一信号面自带;零自动推进停点语义承载 | 对外=开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented/curio_resolved);由 AdventureRunner 关卡完成受理时调用 | ConfigDB;注入 CurioDeck(抽签/摘牌)与 HeroLedger(落账);随机只经 CurioDeck 的 reward_pick 封闭流 | 无 | | **CurioStop**(`core/curio_stop.gd`,三选一停点件,已实装(2026-10-27 迭代 39 拆件批)) | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途)、刷新三源账(刷新件 X/营地池 Y/刷新券存量/付费刷新计数)、兑付方法族(pick/refresh/redeem/skip)、凭证出箱(RAP/时沙待注入);三选一信号面自带;零自动推进停点语义承载 | 对外=开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented/curio_resolved);由 AdventureRunner 关卡完成受理时调用 | ConfigDB;注入 CurioDeck(抽签/摘牌)与 HeroLedger(落账);随机只经 CurioDeck 的 reward_pick 封闭流 | 无 |
| **breakpoint_store**(`core/breakpoint_store.gd`,冒险域·断点组装与回放件,2026-10-29 零豁免裁设计闭合——实现批建件) | 存档断点组装与回放唯一承载(自 adventure_runner 收编 6 成员+函数族):断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入;`resume_adventure` 留宿主薄壳;断点键面零变化 | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) | | **breakpoint_store**(`core/breakpoint_store.gd`,冒险域·断点组装与回放件,已建件——迭代 45) | 存档断点组装与回放唯一承载(自 adventure_runner 收编 6 成员+函数族):断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入(九引用束成员解包为原名成员——保迁移函数体逐字);`resume_adventure` 留宿主(壳+宿主侧回填语句——R2 所有权缝分家);断点键面零变化;实测类变量 15(贴顶禁再增) | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) |
### 功能层 ### 功能层
@@ -119,10 +119,11 @@
| **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 冒烟实证可回放) | | **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——效果引擎全同步解释器) | | **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 值授予前已定) | | **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——装板全同步) | | **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 域·轮循环驱动件,2026-10-29 设计闭合——实现批建件) | 轮循环驱动域唯一承载(自宿主收编 7 成员):结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 构造=引用束注入;段驱动入口由宿主接线调用 | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 | | **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 域·战斗事实件,2026-10-29 设计闭合——实现批建件) | 战斗事实域唯一承载(自宿主收编 14 成员):受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 构造=引用束注入;宿主与段类经注入引用读写 | B1 宿主;A2/A3 只读 | 无 | | **BattleFacts**(`core/battle_facts.gd`,B1 域·战斗事实件,已建件——迭代 44) | 战斗事实域唯一承载(自宿主收编 14 成员):受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 零参直构;宿主与段类经注入引用读写;实测类变量 15(贴顶)/公开方法 4(reset_round+三 mark 写口) | B1 宿主;A2/A3 只读 | 无 |
| **settlement/ 五段段类**(B1 域·结算段件) | B1 每轮结算序列的五段逻辑件(授予/增益减益/战斗/衰减/判定——细则见下「战斗域装配现状」节) | 段处理入口(B1 接线调用);可直接 `new()` 单测 | B2/B3/B4 兑付面与 B1 轮 facts 缓冲(各段按职责) | 无 | | **settlement/ 五段段类**(B1 域·结算段件) | B1 每轮结算序列的五段逻辑件(授予/增益减益/战斗/衰减/判定——细则见下「战斗域装配现状」节) | 段处理入口(B1 接线调用);可直接 `new()` 单测 | B2/B3/B4 兑付面与 B1 轮 facts 缓冲(各段按职责) | 无 |
| **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1(直构 BattleOrchestrator 接线战斗)、ConfigDB LevelTable | 无 | | **level_runner**(C1-lite,切片已建) | 关卡域生命周期(节点链推进、共享 B4 + HP 携带、关卡末 level_clear) | 节点推进接口;LevelTable(`level.tres`——节点 enum {battle,reward,rest,boss}、末节点必须 Boss、ConfigDB 跨表校验⑧) | B1(直构 BattleOrchestrator 接线战斗)、ConfigDB LevelTable | 无 |
| **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 | 无 | | **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 | 无 |
@@ -131,7 +132,7 @@
### 战斗域装配现状(迭代 27——2026-10-17 用户裁写入;RunLedger 段已随迭代 39 拆件批退役,四账去向见下「冒险域拆分(现状登记)」节) ### 战斗域装配现状(迭代 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),本节为现状登记) ### 战斗编排域拆件定稿(2026-10-26 用户裁写入——gdstyle 存量债波 1 设计闭合;同日独立审查(grilling+godot-master 双子代理)阻断 8 条去重 6 组全折入本节,v1.18;2026-10-28 迭代 40 实现批合回(合并提交 514457d1),本节为现状登记)
@@ -162,17 +163,23 @@
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API | | 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 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 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 | | **组合根拆件**(呈现侧写口子件:curio_interact_controller / camp_shop_presenter / treasure_interact_controller) | 代组合根执行核心写调用(冒险节点后续推进/营地购买与刷新/道具使用——依赖与回调由 main 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 |
| **ui_registry**(`presentation/ui_registry.gd`,界面注册面,2026-10-29 零豁免裁设计闭合——实现批建件) | 呈现层接口面唯一承载:层序合同(LAYER_CONTRACT)迁驻与合同寻址、三域句柄容器 ui_screens(9 屏壳与覆盖层句柄)/ui_battle(5 战区句柄)/ui_modals(13 模态句柄+ModalFlow);组合根经其一级寻址接线,探针挂载假设迁移为 `main.ui.<域容器>.<句柄>`;七个探针别名/转发成员随批消灭 | 三容器属性+合同寻址口(RefCounted 门面,不挂树、零会话期引用) | 构造注入:三屏壳、覆盖层、战区句柄、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) |
| **board_hover**(`views/board_hover.gd`,悬停预承诺呈现域件,尾波清偿批 D2 设计闭合——实现批建件) | 悬停反馈域唯一承载(自 board_view 收编 11 函数族+_hover/_last_click 两成员):三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚;_r0 出生展开区成员留宿主 | 构造=引用束注入(cells/minesweeper/diggable 引用+光标写入与 hover_forbidden 注入回调);三入站口留宿主委托;hover_probe 读面留宿主 | board_view(宿主);输入事件经宿主入站口 | 无(RefCounted) | | **UiCampModals**(`presentation/ui_camp_modals.gd`,营地域句柄子容器,已建件——迭代 47 卡⑥) | ui_modals 营地域六句柄唯一容器(场景根/四子界面/Esc 浮窗/HUD 栏),句柄寻址两段式 `modals.camp.*`;ui_modals 平铺面收窄 | 子容器属性+六句柄只读投影;main_ui_build 装配交付 | 构造注入(六视图句柄);ui_modals(父容器挂载) | 无(RefCounted) |
| **cell_base_layer/cell_mark_overlay**(`views/`,单格两绘制域件,2026-10-29 设计闭合——实现批建件) | 单格绘制域拆分(自 board_cell_unit 状态件 `_CellPaintState` 收编,入参与该层绘制命令同件):基座层(底面/描边/立面/投影/接触带/倒角,12 成员)与标识叠加层(标记/字形/徽章/封印/点迹/高亮/退色,13 成员);宿主 _draw 退为六步时序器,类变量 30→10 | 构造注入;绘制命令由宿主 _draw 按层序调用 | board_cell_unit(宿主);gdstyle 键各文件独立计数(12/13 均 ≤15) | 无(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 树 |
| **standee_vitals/standee_motion**(`views/`,站桩两状态件,2026-10-29 设计闭合——实现批建件) | 站桩状态域拆分(自 `_StandeeState` 真实 25 成员收编):身份命值(hp/atk/状态值族,12 成员)与资产演出(动作/姿态/位移演出族,11 成员);三个演出探针计数器留宿主,宿主类变量 19→10 | 构造注入;st(沿用)/mo 两实例由宿主持有 | standee_body(宿主);battle_stage_view 写入面逐字保持 | 无(RefCounted) | | **营地四子界面视图**(`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) |
| **HudPresenter**(`presentation/hud_presenter.gd`,迭代 37 加注) | 战斗屏**数据投影+文案拼装静态族**——核心状态→呈现文案的全部翻译集中于此(B/C/D 区文案与投影),静态无状态 | `refresh(…)` 全量重查装配(组合根每次动作/结算返回后调用,漏调即陈旧呈现)+只读静态投影族 | 核心读取只走 `settlement.presentation_snapshot()` 成文快照面(禁链式穿透结算内部);ConfigDB display_name | 无(RefCounted 静态) | | **HudPresenter**(`presentation/hud_presenter.gd`,迭代 37 加注) | 战斗屏**数据投影+文案拼装静态族**——核心状态→呈现文案的全部翻译集中于此(B/C/D 区文案与投影),静态无状态 | `refresh(…)` 全量重查装配(组合根每次动作/结算返回后调用,漏调即陈旧呈现)+只读静态投影族 | 核心读取只走 `settlement.presentation_snapshot()` 成文快照面(禁链式穿透结算内部);ConfigDB display_name | 无(RefCounted 静态) |
| **board_resolvers**(`presentation/board_resolvers.gd`,迭代 37 拆出) | 棋盘每格渲染数据的只读解析回调四函数(奖励徽章身份——挖掘期 Active 相位内、地形变异数字遮蔽与角标、调试加成生效值)——有态绑定件,与无态静态投影族 HudPresenter 职责互斥 | 构造注入 db、调试浮窗与结算活态取值,产出绑定 Callable 供组合根接线 | ConfigDB / DebugOverlay / 结算活态取值 Callable | 无(纯投影) | | **board_resolvers**(`presentation/board_resolvers.gd`,迭代 37 拆出) | 棋盘每格渲染数据的只读解析回调四函数(奖励徽章身份——挖掘期 Active 相位内、地形变异数字遮蔽与角标、调试加成生效值)——有态绑定件,与无态静态投影族 HudPresenter 职责互斥 | 构造注入 db、调试浮窗与结算活态取值,产出绑定 Callable 供组合根接线 | ConfigDB / DebugOverlay / 结算活态取值 Callable | 无(纯投影) |
| **main_ui_build**(`presentation/main_ui_build.gd`,迭代 37 拆出) | `_build_ui` 八步拆分件——组合根 UI 装配分步承接(三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开);main 侧留同名薄委托,装配序与 z 序零改动 | 分步静态函数(成员访问经 root 显式传入) | 层序逻辑不在此件——唯一事实源仍是 `main.LAYER_CONTRACT` + `_apply_layer_contract()` | 同 D1(Control 装配面) | | **main_ui_build**(`presentation/main_ui_build.gd`,迭代 37 拆出) | `_build_ui` 八步拆分件——组合根 UI 装配分步承接(三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开);main 侧留同名薄委托,装配序与 z 序零改动 | 分步静态函数(成员访问经 root 显式传入) | 层序逻辑不在此件——唯一事实源=`ui_registry.LAYER_CONTRACT`+`ui.apply_layer_contract(root)`(迭代 44 迁驻注册面) | 同 D1(Control 装配面) |
| **main_adventure_flow**(`presentation/main_adventure_flow.gd`,迭代 37 拆出) | main 冒险流程族——菜单开局/续玩后的冒险编排接线、节点事件分发、终局面与选路屏开合;逐字搬运,main 侧留同名薄委托(探针 `main.X` 路径零迁移) | 流程静态函数(root 显式) | 局态全局住 run_scope 节点——本件零成员态、零新增信号连接(连接语句随原段搬运) | 无 | | **main_adventure_flow**(`presentation/main_adventure_flow.gd`,迭代 37 拆出) | main 冒险流程族——菜单开局/续玩后的冒险编排接线、节点事件分发、终局面与选路屏开合;逐字搬运,main 侧留同名薄委托(探针 `main.X` 路径零迁移) | 流程静态函数(root 显式) | 局态全局住 run_scope 节点——本件零成员态、零新增信号连接(连接语句随原段搬运) | 无 |
| **main_overlay_flow**(`presentation/main_overlay_flow.gd`,迭代 37 拆出) | main 浮层流程族——设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 全部开合走组合根模态单口(`root._open_modal/_close_modal`)——本件零成员态 | 同 D1 | | **main_overlay_flow**(`presentation/main_overlay_flow.gd`,迭代 37 拆出) | main 浮层流程族——设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 全部开合走组合根模态编排口(`root.ui.modals.flow.open/close`)——本件零成员态 | 同 D1 |
| **main_debug_flow**(`presentation/main_debug_flow.gd`,迭代 37 拆出) | main 调试流程族——调试浮窗开合与上下文同步、批量模拟入口、两层调试对局起手、诊断转储;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 调试会话状态机归 debug_session.gd——本件只接线与出 UI,零成员态 | 同 D1 | | **main_debug_flow**(`presentation/main_debug_flow.gd`,迭代 37 拆出) | main 调试流程族——调试浮窗开合与上下文同步、批量模拟入口、两层调试对局起手、诊断转储;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 调试会话状态机归 debug_session.gd——本件只接线与出 UI,零成员态 | 同 D1 |
| **TilePalette**(`presentation/views/tile_palette.gd`,迭代 37 拆出) | 瓦片排版/装载:高墙/地面瓦片整批装载与运行时镜像合成、地板明度归一、数字墨族按板自适应判据、格坐标确定性散列与十六正交情形墙瓦片查表 | 查表族(读件持棋盘引用——棋盘视图 rebuild 经 `bind_board` 同步);装载缓存七成员持于件内(同板型零重载、换板重载时序同源) | 零绘制面、零玩法数据写入(绘制调用面留棋盘视图);纹理路径单点在件内 | Resource 纹理加载——LOW | | **TilePalette**(`presentation/views/tile_palette.gd`,迭代 37 拆出) | 瓦片排版/装载:高墙/地面瓦片整批装载与运行时镜像合成、地板明度归一、数字墨族按板自适应判据、格坐标确定性散列与十六正交情形墙瓦片查表 | 查表族(读件持棋盘引用——棋盘视图 rebuild 经 `bind_board` 同步);装载缓存七成员持于件内(同板型零重载、换板重载时序同源) | 零绘制面、零玩法数据写入(绘制调用面留棋盘视图);纹理路径单点在件内 | Resource 纹理加载——LOW |
| **HeroSprite**(`presentation/views/hero_sprite.gd`,迭代 37 拆出) | 英雄精灵四态与落位装配(落位与位移演出、撞墙帧闪、挖掘弹跳、脚底锚与持握手位出口) | 由棋盘视图 `_init` 建为子件(注入世界根与壳层容器引用)——精灵本体经世界根 ensure_hero 建于 SubViewport 世界内,本件只驱动其状态 | BoardWorld 世界根;素材缺位=零精灵(棋盘视图字形「人」回退路径在役) | Node2D / create_tween(Tween 系 Node 方法——MEDIUM 域对照 breaking-changes.md) | | **HeroSprite**(`presentation/views/hero_sprite.gd`,迭代 37 拆出) | 英雄精灵四态与落位装配(落位与位移演出、撞墙帧闪、挖掘弹跳、脚底锚与持握手位出口) | 由棋盘视图 `_init` 建为子件(注入世界根与壳层容器引用)——精灵本体经世界根 ensure_hero 建于 SubViewport 世界内,本件只驱动其状态 | BoardWorld 世界根;素材缺位=零精灵(棋盘视图字形「人」回退路径在役) | Node2D / create_tween(Tween 系 Node 方法——MEDIUM 域对照 breaking-changes.md) |
@@ -190,7 +197,7 @@
### 三屏壳与组合根句柄面 ### 三屏壳与组合根句柄面
- 呈现场景拆为三屏壳:MenuScreen(开始菜单屏)/CampScreen(营地屏)/AdventureScreen(冒险屏——内含关卡地图与战斗两容器)。 - 呈现场景拆为三屏壳:MenuScreen(开始菜单屏)/CampScreen(营地屏)/AdventureScreen(冒险屏——内含关卡地图与战斗两容器)。
- 屏成员寻址自实现批起迁经界面注册面(**2026-10-29 零豁免裁+注册面设计闭合——实现批建件**):探针挂载假设 `main.X` 迁移为 `main.ui.<域容器>.<句柄>`(ui_screens/ui_battle/ui_modals 三域容器,约 867 处探针与系统测试引用随批改址);原 S13 豁免登记〔2026-10-27「组合根类变量面即探针契约面」〕注销;main.gd 类变量 49→15,注册面形态见「模块归属·呈现层」表 ui_registry 行;局态一律住 RunScope 节点,禁在屏/视图侧持有局态引用。 - 屏成员寻址自实现批起迁经界面注册面(**已建件——迭代 44**):探针挂载假设 `main.X` 迁移为 `main.ui.<域容器>.<句柄>`(ui_screens/ui_battle/ui_modals 三域容器,约 867 处探针与系统测试引用随批改址);原 S13 豁免登记〔2026-10-27「组合根类变量面即探针契约面」〕注销;main.gd 类变量 49→15,注册面形态见「模块归属·呈现层」表 ui_registry 行;ModalFlow 实例随 UiModals 容器构造建立(装配早期注入点依赖其先在座);取证探针 shot_probe/fx_shot_probe 随批改址;局态一律住 RunScope 节点,禁在屏/视图侧持有局态引用。
- **组合根拆件**=组合根构造注入、代组合根执行核心写调用的呈现侧子件(现行三件:curio_interact_controller/camp_shop_presenter/treasure_interact_controller——冒险节点后续推进、营地购买与刷新、道具使用)。视图禁直连拆件;核心写调用的可达路径仍是「视图→信号→组合根(含拆件)→核心」,依赖规则与组合根单点装配不变。成员见「模块归属·呈现层」表。 - **组合根拆件**=组合根构造注入、代组合根执行核心写调用的呈现侧子件(现行三件:curio_interact_controller/camp_shop_presenter/treasure_interact_controller——冒险节点后续推进、营地购买与刷新、道具使用)。视图禁直连拆件;核心写调用的可达路径仍是「视图→信号→组合根(含拆件)→核心」,依赖规则与组合根单点装配不变。成员见「模块归属·呈现层」表。
### 视觉层装配合同 ### 视觉层装配合同
@@ -249,18 +256,17 @@
| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` | | **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` |
| **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — | | **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — |
口径三条:**域划分的唯一出处是 `tools/run_all_gates.sh`**(`--code`/`--docs`/不带参数=全量),本表是它的现状登记、不是第二份清单;改闸件本身或一次改动跨两域 → 跑全量档(§6.2 共享件例外);合回闸按 merge diff 的文件性质推出应验的域(`game/` 内 `.md` 归文档域),再校验回执的 `domain` 字段与新增红。代码域跑前先过**素材齐盘预检**——`game/assets/art/**` 与一切 `*.import` 边车都不入 git(2026-09-29 裁定),worktree 冷检出必空盘,空盘态冒烟 V1 成片假红、渲染闸更是一段 golden 都不比。 口径三条:**域划分的唯一出处是 `tools/run_all_gates.sh`**(`--code`/`--docs`/不带参数=全量),本表是它的现状登记、不是第二份清单;改闸件本身或一次改动跨两域 → 跑全量档(§6.2 共享件例外);开发期手动档按本批改动域选 `--code`/`--docs`,合回后 CI 恒跑全量档。代码域跑前先过**素材齐盘预检**——`game/assets/art/**` 与一切 `*.import` 边车都不入 git(2026-09-29 裁定),worktree 冷检出必空盘,空盘态冒烟 V1 成片假红、渲染闸更是一段 golden 都不比。
### 车道分层执法(2026-10-26 用户裁「按路径切车道,取代一切改动走 worktree」) ### 车道分层执法(2026-10-31 用户裁恢复「一切改动走 worktree」)
改动车道与改动域同一判据(文件性质):代码面=worktree 批车道,文档面=主工作区直改车道。执法全机械——判据只有文件路径性质与 git 图,不含「改动大小」等语义判断;「预期大改」由用户开工点名自愿开 worktree,不进规则。 一切改动走独立 worktree,主工作区只作合并落点。执法全机械——判据只有 git 图与路径(豁免面 `.zcode/`、`.qoder/` 不入 git,主区直改),不含「改动大小」等语义判断。
| 执法层 | 载体 | 职责 | 判据 | | 执法层 | 载体 | 职责 | 判据 |
|---|---|---|---| |---|---|---|---|
| **git pre-commit 钩子** | `tools/hooks/pre-commit`(版本化本体,`core.hooksPath` 挂载;在位性由 run_all_gates.sh 跑批自检——失位即红) | 直改车道闸机检:staged 不含代码面的提交,提交点现场跑 `run_all_gates.sh --docs`(秒级文本扫描),绿落地红拦;staged 含代码面时主工作区=拦(代码面唯一合法路径=worktree 批合回)、任务 worktree=跳过(归批末闸+合回闸,不打扰迭代批中间提交) | staged 文件性质+主区/worktree 判定(git-dir 与 common-dir 同一=主区) | | **worktree-guard**(ZCode PreToolUse) | `.zcode/hooks/worktree-guard.sh`(本机件,不入 git) | agent 侧车道纪律:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免);`git commit --no-verify` 拦截(该旗标仅属用户);iter 分支合回一卡一提交数卡校验;规则闸 | 路径性质+分支名+MERGE_HEAD+git 图 |
| **worktree-guard**(ZCode PreToolUse) | `.zcode/hooks/worktree-guard.sh`(本机件,不入 git) | agent 侧车道纪律:主工作区代码面的 Edit/Write 与 staged 含代码面的 commit 拦截;`git commit --no-verify` 拦截(该旗标仅属用户);合回闸回执六况校验 | 路径性质+分支名+回执 JSON+git 图 |
取舍依据:失败代价不对称——docs 红是死链/术语/格式,下次任何跑批对基线比对必暴露、秒级修复,故文档面轻执法(commit 点闸、无回执流程);代码面红是运行时行为坏,保留「worktree+批末域闸+回执硬绑定」全套。git 层钩子对一切提交者生效(含用户手工提交,紧急出口 `--no-verify`);ZCode 层只约束 agent。子代理覆盖面(2026-10-26 冷启动评估实测):子代理的工具调用不经过 ZCode 层守卫(代码面 Edit 未被拦、`--no-verify` 探针未被拦)——ZCode 层实际只覆盖主会话工具调用,git 层因此是唯一覆盖一切提交者的执法层;残余暴露面=蓄意 `--no-verify`(pre-commit 族钩子被该旗标整族跳过,git 层无法自防),属故意越权而非遗忘型失误,与防遗忘的设计目标正交。 验证收拢为合回后本地 CI 全量(post-merge 触发),commit 点零执法;主工作区防污染由 ZCode 层守卫承担——只约束 agent,用户手工改动不受限。子代理覆盖面(2026-10-26 冷启动评估实测):子代理的工具调用不经过 ZCode 层守卫——ZCode 层实际只覆盖主会话工具调用;残余暴露面=子代理写主工作区与蓄意 `--no-verify`(该旗标整族跳过 git 层钩子),属故意越权而非遗忘型失误,与防遗忘的设计目标正交。
### 三条判定硬闸现行状态 ### 三条判定硬闸现行状态
@@ -318,11 +324,11 @@
本节为测试域(测试载体、判定口径、静态扫描域)的事实源。新增违规案例仍须先补 static_scan 规则再修码(code-conventions 缺陷-规范对照表纪律)。 本节为测试域(测试载体、判定口径、静态扫描域)的事实源。新增违规案例仍须先补 static_scan 规则再修码(code-conventions 缺陷-规范对照表纪律)。
**S13 测量面豁免注销(2026-10-29 零豁免裁)**:原循环内 `await` 豁免〔2026-10-27〕注销——tune_sim_controller 改 process_frame 信号常驻连接+步进状态机(每帧一局、帧相位逐点对齐,设计闭合、实现批建件);随批申报行为改动一处:运行态收尾由「每局置假」还原为「整批收尾单次置假」(消除与 wd8/ui_tune_probe 断言面的静态矛盾)。 **S13 测量面豁免注销(2026-10-29 零豁免裁)**:原循环内 `await` 豁免〔2026-10-27〕注销——tune_sim_controller 改 process_frame 信号常驻连接+步进状态机(每帧一局、帧相位逐点对齐,已建件——迭代 44 卡③);运行态收尾现行形态=run() 尾每批一次执行(卡③ v1.0 申报的「每局置假还原」前提已被迭代 41 测试债批 215da315 先行消化,行为改动申报随 v1.1 停卡重裁剥离)。
**S13 豁免第三条注销(2026-10-29 零豁免裁)**:原 battle_orchestrator max-class-variables 豁免〔2026-10-28 迭代 40 卡①〕注销——RoundCycle/BattleFacts 两新件设计闭合(宿主 38→15,实现批建件,见「战斗编排域拆件定稿」节轴丁)。 **S13 豁免第三条注销(2026-10-29 零豁免裁)**:原 battle_orchestrator max-class-variables 豁免〔2026-10-28 迭代 40 卡①〕注销——RoundCycle/BattleFacts 两新件设计闭合(宿主实测 15,已建件——迭代 44 卡② 合回,见「战斗编排域拆件定稿」节轴丁)。
**gdstyle 计数截断缺陷登记(2026-10-29 两设计批独立实证)**:gdstyle v0.4.0 对文件内 inner class 的成员计数存在截断——内嵌类中带行尾注释的属性声明截断其后续成员计数(standee_body 实测 31 声明仅记 19、board_cell_unit 33 仅记 30)。基线登记值属欠估;闸机扫描与基线同源、判定口径自洽不误报,债的真值以实查为准——零豁免各批按真实口径设计(实数低于阈值,不依赖解析缺陷)。登记待办:向 gdstyle 上游报 issue。 **gdstyle 计数截断缺陷登记(2026-10-29 两设计批独立实证)**:gdstyle v0.4.0 对文件内 inner class 的成员计数存在截断——内嵌类中带行尾注释的属性声明截断其后续成员计数(standee_body 实测 31 声明仅记 19、board_cell_unit 33 仅记 30)。基线登记值属欠估;闸机扫描与基线同源、判定口径自洽不误报,债的真值以实查为准——零豁免各批按真实口径设计(实数低于阈值,不依赖解析缺陷)。**清偿结果(2026-10-31)**:board_cell_unit 30→10、standee_body 19→10 已随迭代 45 真实清偿,基线 refresh 已收账(欠估口径旁证=33/31 声明与实测吻合)。登记待办:向 gdstyle 上游报 issue(仍在册)。
--- ---
+5 -5
View File
@@ -18,7 +18,7 @@
写文档像向朋友当面解释你的游戏: 写文档像向朋友当面解释你的游戏:
1. **完整句子优先**:一段只说一件事,长句拆短句。 1. **完整句子优先**:一段只说一件事,长句拆短句。
2. **禁止碎片笔记体**:不写压缩碎片,不把 `A→B→C` 箭头链当叙述(流程交给字符画,见第 3 节),不用只有作者自己懂的缩略语。 2. **禁止碎片笔记体**:不写压缩碎片,不把 `A→B→C` 箭头链当叙述(流程交给图,见第 3 节),不用只有作者自己懂的缩略语。
3. **每节人话开头**:每小节第一段先用白话说清"这是什么、为什么需要它";只读各节第一段就能看懂全文故事线。 3. **每节人话开头**:每小节第一段先用白话说清"这是什么、为什么需要它";只读各节第一段就能看懂全文故事线。
4. **术语首现即解释**:专业术语或项目概念首次出现用一句话解释;玩家可见专属名词(如"显影尘")同样要说清。 4. **术语首现即解释**:专业术语或项目概念首次出现用一句话解释;玩家可见专属名词(如"显影尘")同样要说清。
5. **叙述优先于引用**:AC-x、F-x 等编号引用不进正文叙述,只出现在验收标准与附录。 5. **叙述优先于引用**:AC-x、F-x 等编号引用不进正文叙述,只出现在验收标准与附录。
@@ -30,11 +30,11 @@
|---|---|---| |---|---|---|
| 规则的来龙去脉、行为逻辑 | 散文段 | 每段 ≤240 字,一段一件事 | | 规则的来龙去脉、行为逻辑 | 散文段 | 每段 ≤240 字,一段一件事 |
| 枚举、数值、边界、依赖 | 表格 | 表格只放可列举的事实;禁把需要解释的内容塞进单元格让读者自己拼 | | 枚举、数值、边界、依赖 | 表格 | 表格只放可列举的事实;禁把需要解释的内容塞进单元格让读者自己拼 |
| 流程、状态流转 | 字符画(代码块) | 禁用散文逐步描述流程 | | 流程、状态流转、结构关系 | 文档内嵌 HTML/SVG(默认)· Mermaid(网页发布保底)· 字符画(无渲染环境保底) | 禁用散文逐步描述流程;默认在文档内嵌 HTML/SVG(表现力优先,本地预览即渲染,图体行数计入节点篇幅);发布到会过滤内嵌 HTML 的网页端时改 Mermaid;无渲染查看环境用字符画 |
| 公式 | 结论先行 | 先一行加粗结论,再放公式代码块、变量表与验算表 | | 公式 | 结论先行 | 先一行加粗结论,再放公式代码块、变量表与验算表 |
| 体验锚点 | 关键词表 | 支柱/关键词/机制承诺 三列表,禁长篇情绪描述 | | 体验锚点 | 关键词表 | 支柱/关键词/机制承诺 三列表,禁长篇情绪描述 |
节点内顺序:先人话,再表格枚举,再字符流程图,最后公式举证。**规则两段式**:先写"人话阶段"(作用于哪个系统、什么情况下、谁触发、触发后发生什么),再写"举证阶段"(逐效果给出计算方法;纯流程规则明写"无计算")。规则名禁止纯名词短语(反面例:"获取路由")。 节点内顺序:先人话,再表格枚举,再流程图(内嵌 HTML/SVG 或字符画),最后公式举证。**规则两段式**:先写"人话阶段"(作用于哪个系统、什么情况下、谁触发、触发后发生什么),再写"举证阶段"(逐效果给出计算方法;纯流程规则明写"无计算")。规则名禁止纯名词短语(反面例:"获取路由")。
## 4. 用语纪律(见名知意 · 所见即所得 · 禁自造黑话) ## 4. 用语纪律(见名知意 · 所见即所得 · 禁自造黑话)
@@ -136,7 +136,7 @@
### 12.1 待办清单条目格式与状态机 ### 12.1 待办清单条目格式与状态机
待办清单是流水线的唯一登记处:要做的事先登记一条,随流水线段推进改状态,做完整条删除。一条条目分两层——**标题=登记元数据**(何时登记、从哪来、紧急度,登记后不再变),**正文=四个属性**(随进程演进)。四个属性各管一件事:`实况` 管凭据,`缺口` 管这条的**输入**(还缺哪一次设计),`改动面` 管这条的**产出**(要动哪些面),`状态` 管这条走到流水线哪一段。 待办清单是流水线的唯一登记处:**现在不做、以后才做的事**登记一条,随流水线段推进改状态,做完整条删除。条目记录某件事走到了哪一段,不是各段的开工前置。一条条目分两层——**标题=登记元数据**(何时登记、从哪来、紧急度,登记后不再变),**正文=四个属性**(随进程演进)。四个属性各管一件事:`实况` 管凭据,`缺口` 管这条的**输入**(还缺哪一次设计),`改动面` 管这条的**产出**(要动哪些面),`状态` 管这条走到流水线哪一段。
标题格式(尖括号处按实际填写,方括号内为可选段): 标题格式(尖括号处按实际填写,方括号内为可选段):
@@ -173,7 +173,7 @@
文档线:待文档设计 → 文档设计中 → 文档设计已闭合 文档线:待文档设计 → 文档设计中 → 文档设计已闭合
美术线:待美术设计 → 美术设计中 → 美术设计已闭合 美术线:待美术设计 → 美术设计中 → 美术设计已闭合
下游共用序列(需求改动):待写卡 → 已写卡 → 已编迭代 N → 完成即整条删除 下游共用序列(需求改动):待写卡 → 已写卡 → 已编迭代 N → 完成即整条删除
直改序列(工程改动/文档设计改动/美术素材改动):登记 → 待执行 → 完成即整条删除(经设计讨论的可前置闭合段,如「文档设计已闭合+待执行」) 直改序列(工程改动/文档设计改动/美术素材改动):待执行 → 完成即整条删除(经设计讨论的可前置闭合段,如「文档设计已闭合+待执行」)
累积示例:文档设计已闭合+待美术设计+已写卡 累积示例:文档设计已闭合+待美术设计+已写卡
``` ```
+13 -13
View File
@@ -1,28 +1,28 @@
# 流程规范(Dungeon Sweeper) # 流程规范(Dungeon Sweeper)
> 约束范围:一切批次的流程纪律——车道(代码面 worktree 批/其余主区直改)、五段流水线、批次工作流、测试执行细则、像素件工作纪律。 > 约束范围:一切批次的流程纪律——车道(一切改动走独立 worktree,主区只作合并落点)、五段流水线、批次工作流、测试执行细则、像素件工作纪律。
> 分工:硬性红线的一句话索引在 AGENTS.md「硬性红线」;本文承载其操作细则;批次开工前通读本文。 > 分工:硬性红线的一句话索引在 AGENTS.md「硬性红线」;本文承载其操作细则;批次开工前通读本文。
**开工任何批次前必须通读本文。** **开工任何批次前必须通读本文。**
## 1. 分支纪律(车道铁律) ## 1. 分支纪律(worktree 铁律)
改动按文件性质分两车道(2026-10-26 用户裁,取代 2026-09-19 立的「一切改动走 worktree」铁律;判据与 §3 第 8 条改动域划分同一): 主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;2026-10-30 的「主区直改+直提」车道与 commit 点 pre-commit 直改闸随之退役;验证模型不动——合回后本地 CI 仍是唯一强制验证点):
1. **worktree 批车道(唯一强制面=代码面)**:一切触及代码面(`game/` 下非 `.md` 的行为载体:代码、测试脚本、探针、场景、数据表、素材)的改动——迭代实现批与触及代码面的工程改动(如纯测试维护)——先建独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;所有改动与验证都在 worktree 内进行(未跟踪文件不会出现在新 worktree,需手工搬运);建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。迭代批开工细则见 §3 与 iter 技能 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. **worktree 批收尾**:批次收口顺序固定为**冻结 → 件核 → 并 master → 重塑 → 跑一次域闸 → 合回**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先 `git merge master` 解冲突(必须在跑闸之前,闸跑完再并=那次闸白跑);**重塑**=迭代分支(iter<N> 形)在跑闸前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代工程小批免重塑;然后按本批改动域跑**一次** `tools/run_all_gates.sh`(见 §3.9),全绿后在主工作区 `git merge --no-ff <任务分支>`(合回闸对迭代分支数卡校验,未归卡提交拦下),再 `git worktree remove`+删分支。闸后代码面不得再改一个字节(回执作废须重跑),只允许追加 `reports/`、`design/` 等文档件提交。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑 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. **直改车道(代码面之外的一切面)**:`design/`、`reports/`、`tools/`、根规则与清单文件、`game/` 内 `.md`、`.zcode/`、`.qoder/`——主工作区直接改、直接提交,无 worktree、无冻结/件核/合并仪式。验收=commit 点 git pre-commit 钩子(`tools/hooks/pre-commit`,经 `core.hooksPath` 挂载,在位性由 run_all_gates.sh 跑批自检)现场跑 `tools/run_all_gates.sh --docs`(秒级文本扫描):绿即落地,红即拦下,修红后重新 commit 钩子自动重跑;跑完把闸输出呈报对话即完成义务。预期大体量改动(跨文档结构重构等)时由用户开工点名自愿开 worktree,非规则强制 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. **执法分层(全机械,零自觉依赖)**:①git pre-commit 钩子——直改车道的闸机检,并对主工作区直提代码面的提交一律拦(代码面唯一合法路径=worktree 批合回);任务 worktree 内的代码面提交跳过(归批末闸+合回闸管,不打扰迭代批中间提交)。②`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区代码面的 Edit/Write 与 staged 含代码面的 commit 拦截、`git commit --no-verify` 拦截(该旗标仅属用户)、合回闸回执六况校验(§3.9)。git 层钩子对一切提交者生效(含用户手工提交,紧急出口 `--no-verify`);ZCode 层只约束 agent 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. 五段流水线 ## 2. 五段流水线
**需求改动**(游戏代码,含随逻辑写/改的测试)按五段流水线走:**登记 → 文档设计/美术设计(两条设计线)→ 写需求卡片 → 迭代编排 → 实现**;工程/文档设计/美术素材三类改动不走写卡与编排段,走「登记 →(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸 → 完成删条目」(车道细则见 §1)。两层口径(2026-10-25 用户立):需求卡片=需求单位的实施文档,任何工作量皆可写、皆可编入迭代;工作量限制只属迭代层——迭代估时总量 <5 个工作日=「冻结待凑批」,不可开工。段间不自动流转,每段转进由用户发令;每段一个技能入口(五者均装于项目 `.zcode/skills/`:doc-design/asset-ingest/req-card/iter-plan/iter),登记不立技能(条目格式由流水线闸强制)。设计工作在会话内进行、用户主导、禁无监管自跑。 **需求改动**(游戏代码,含随逻辑写/改的测试)按五段流水线走:**登记 → 文档设计/美术设计(两条设计线)→ 写需求卡片 → 迭代编排 → 实现**;工程/文档设计/美术素材三类改动不走写卡与编排段,走「(如需设计讨论)→ 用户发令 → 独立 worktree 直改 → 合回 CI」(车道细则见 §1)。两层口径(2026-10-25 用户立):需求卡片=需求单位的实施文档,任何工作量皆可写、皆可编入迭代;工作量限制只属迭代层——迭代估时总量 <5 个工作日=「冻结待凑批」,不可开工。段间不自动流转,每段转进由用户发令;每段一个技能入口(五者均装于项目 `.zcode/skills/`:doc-design/asset-ingest/req-card/iter-plan/iter),登记不立技能(条目格式由流水线闸强制)。设计工作在会话内进行、用户主导、禁无监管自跑。
| 段 | 入口技能 | 产出 | 出入口铁律 | | 段 | 入口技能 | 产出 | 出入口铁律 |
|---|---|---|---| |---|---|---|---|
| 一·登记 | 无(闸管格式) | 待办清单条目 | 条目=标题登记元数据(日期/来源/可选优先级)+正文四属性(实况/缺口/改动面/状态);标题登记后不再变,正文随进程演进。格式与状态机权威=doc-conventions 第 12 节 | | 一·登记 | 无(闸管格式) | 待办清单条目 | 条目=标题登记元数据(日期/来源/可选优先级)+正文四属性(实况/缺口/改动面/状态);标题登记后不再变,正文随进程演进。格式与状态机权威=doc-conventions 第 12 节 |
| 二·文档设计/美术设计 | doc-design(文档线,兼任反哺点写入)/asset-ingest(美术录入;样张素材由用户主导产出) | GDD 文档或 art-bible/过审样张素材 | 用户提起或从待办清单取出;会话内进行、用户主导、禁无监管自跑;经用户点头确认心智一致才许写入文档;完成判据=待裁点清零,带未裁点的是讨论稿;设计线闭合凭据记入待办清单实况清单(文档线=文档路径+版本号,美术线=art-bible 条款版本);缺哪条线写在缺口属性,闭合与否写在状态段 | | 二·文档设计/美术设计 | doc-design(文档线,兼任反哺点写入)/asset-ingest(美术录入;样张素材由用户主导产出) | GDD 文档或 art-bible/过审样张素材 | 用户提起或从待办清单取出;会话内进行、用户主导、禁无监管自跑;经用户点头确认心智一致才许写入文档;完成判据=待裁点清零,带未裁点的是讨论稿;设计线闭合凭据记入待办清单实况清单(文档线=文档路径+版本号,美术线=art-bible 条款版本);缺哪条线写在缺口属性,闭合与否写在状态段 |
| 三·写需求卡片 | req-card | 需求卡片 | 设计线闭合后用户发令;新玩法/新系统/新内容必须有设计定稿前置,例外=需求性工程条目(缺陷修复/重构等改游戏行为)凭实况里的证据指认跳过设计线;配套维护类(工程债/测试基建/工具维护,不动游戏行为)=工程改动直改类,不走本段,登记后「待执行」由用户发令直改;卡片必经 grilling 多轮复核、零待裁点才成立;写完在待办清单状态记「已写卡」,卡片路径记入实况清单(防遗忘) | | 三·写需求卡片 | req-card | 需求卡片 | 设计线闭合后用户发令;新玩法/新系统/新内容必须有设计定稿前置,例外=需求性工程条目(缺陷修复/重构等改游戏行为)凭实况里的证据指认跳过设计线;配套维护类(工程债/测试基建/工具维护,不动游戏行为)=工程改动直改类,不走本段,由用户发令直改;卡片必经 grilling 多轮复核、零待裁点才成立;写完在待办清单状态记「已写卡」,卡片路径记入实况清单(防遗忘) |
| 四·迭代编排 | iter-plan | roadmap 迭代行 | 编排四查:①每卡卡片文件存在 ②每卡设计闭合凭据齐(或需求性工程条目的证据指认)③估时与冻结线——所选卡估时按区间**中值**求和(单位工作日、写精确值不取整):**卡片任何工作量均可编入**(2026-10-25 用户立:工作量限制属迭代层、非卡片层);编入后迭代总量 <5 个工作日=迭代行状态「冻结待凑批」、不可开工,凑批 ≥5 后由编排段改「待开工」(迭代体量偏好,2026-10-21 用户立、2026-10-25 改冻结制);5–10 个工作日为常规区间;超过 10 个工作日须经用户确认(确认前呈报整批/拆批两案等裁,禁自行拆分或塞入),确认后迭代行随带日期特批标记;超过 20 个工作日禁止编(硬上限,用户确认也不放行,必须拆批)④写完 roadmap 行后跑流水线闸确认绿;编入未完成迭代,新开迭代行经用户确认 | | 四·迭代编排 | iter-plan | roadmap 迭代行 | 编排四查:①每卡卡片文件存在 ②每卡设计闭合凭据齐(或需求性工程条目的证据指认)③估时与冻结线——所选卡估时按区间**中值**求和(单位工作日、写精确值不取整):**卡片任何工作量均可编入**(2026-10-25 用户立:工作量限制属迭代层、非卡片层);编入后迭代总量 <5 个工作日=迭代行状态「冻结待凑批」、不可开工,凑批 ≥5 后由编排段改「待开工」(迭代体量偏好,2026-10-21 用户立、2026-10-25 改冻结制);5–10 个工作日为常规区间;超过 10 个工作日须经用户确认(确认前呈报整批/拆批两案等裁,禁自行拆分或塞入),确认后迭代行随带日期特批标记;超过 20 个工作日禁止编(硬上限,用户确认也不放行,必须拆批)④写完 roadmap 行后跑流水线闸确认绿;编入未完成迭代,新开迭代行经用户确认 |
| 五·实现 | iter | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务 | | 五·实现 | iter | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务 |
@@ -40,7 +40,7 @@
3. **用户发开工令才动手**——需求卡片经用户批准(U0)后方开工 3. **用户发开工令才动手**——需求卡片经用户批准(U0)后方开工
**环境与基线** **环境与基线**
4. **代码面批独立 worktree+拷导入缓存**——触及代码面的批次按 §1 建 worktree;先拷主区 `.godot/` 目录与字体 `.import` 边车再跑测试,否则全量重导入资源近似卡死。检出工作区零测试,直接开工(2026-10-22 频率规则);闸壳每次跑批先自检/自愈本区 user 目录隔离(§1 第 1 条),建不起来即拒跑;直改车道(非代码面)无此项 4. **一切批独立 worktree+拷导入缓存**——按 §1 建 worktree;涉代码域跑闸或测试的批先拷主区 `.godot/` 目录与字体 `.import` 边车(纯文档批免拷),否则全量重导入资源近似卡死。检出工作区零测试,直接开工(2026-10-22 频率规则);闸壳每次跑批先自检/自愈本区 user 目录隔离(§1 第 1 条),建不起来即拒跑
5. **涉数值改动先采改前读数**——与改后同随机种子配对比较并写明逐项比对口径;无数值改动禁跑模拟 5. **涉数值改动先采改前读数**——与改后同随机种子配对比较并写明逐项比对口径;无数值改动禁跑模拟
**实施** **实施**
@@ -48,8 +48,8 @@
7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补 7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
**完成** **完成**
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件——申报=在册需求卡片对照表「验收测试」列提到的测试文件名,推导=闸脚本内套件族映射表按改动文件归族;改动命中公共件/数据表/autoload/数值·存档·RNG·结算触面或映射不到 → 横切回落全部套件;00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降;03 场景探针与 05 渲染闸维持影响面口径(本批 game/ 改动全在测量面 game/tests、game/sim 时记 SKIP)。所选域全绿即可合回 master,全量回归由用户自发发起(合入后无自动兜底)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain`、`scope`(full|related,02 跑子集=related)与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容;guard 只验域覆盖与全绿,不复核选套件清单)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底 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-24 用户裁:合回重跑验的是与回执同一份字节,零信息)——合回落地后 code 批只做两件秒级事:`--editor --quit` 重刷类名缓存(在册坑:.godot 缓存不入 git,与合并后代码失配会假红假绿)+冒烟启动确认可开机(合计约 1 分钟);docs 批零动作。全量验证**每批跑一次**=worktree 批末按域闸,顺序是「先并入 master、再跑闸」(§1 第 2 条);只有两种情形允许把这次跑作废重来——① 闸红且修法动了 `game/`,② 闸已开跑后 master 又被推进(合并将产出未测内容,由合回闸祖先判定拦下,§3.9 第六况)。「跑都跑了再跑一遍保险」「闸后顺手改点 `game/` 再补一跑」都不构成复跑理由;master 上永不跑全量 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 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/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注 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. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置 12. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置
@@ -62,7 +62,7 @@
**MC/verifier 触发细则**:仅在数值验证场景触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**;结构位移类「存档锚」须先向用户请示定性再跑;需求卡片预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。 **MC/verifier 触发细则**:仅在数值验证场景触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**;结构位移类「存档锚」须先向用户请示定性再跑;需求卡片预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。
**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单,且即域划分的唯一出处**),分三档——`--code`/`--docs` 按本批改动域只跑该域闸(闸件清单与域划分只写在脚本头部注释,本文不重列);`--quick` 是全量档的中段自检形态(跳过渲染闸与静态扫描);**不带参数=两域全量**,用于同一批既有代码面又有文档件改动。完成出库按所选域全绿,回执由合回闸按 diff 文件性质推域强制校验。真人实机目检归用户不进自动闸。 **分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单,且即域划分的唯一出处**),分三档——`--code`/`--docs` 按本批改动域只跑该域闸(闸件清单与域划分只写在脚本头部注释,本文不重列);`--quick` 是全量档的中段自检形态(跳过渲染闸与静态扫描);**不带参数=两域全量**。强制验证点=合回后本地 CI 的全量档(§1 第 2 条);分档只服务开发期手动跑批。真人实机目检归用户不进自动闸。
## 5. MC 采集档位细则 ## 5. MC 采集档位细则
+2 -2
View File
@@ -25,9 +25,9 @@
每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮; 每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮;
选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。 选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。
## 4. 提交前验证 ## 4. 验证时机(合回后本地 CI 制,2026-10-30 换代)
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`),按改动域选档、所选域全绿——该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。闸件增减只改脚本。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件,00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降,横切面改动(公共件/数据表/autoload/数值·存档·RNG·结算触面)回落全部套件;所选域全绿即可合回,全量回归由用户自发发起(合入后无自动兜底)。 开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,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. 测试函数怎么写——验收清单条目的落码规范 ## 5. 测试函数怎么写——验收清单条目的落码规范
+1 -1
View File
@@ -15,7 +15,7 @@
| `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) | | `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) |
| `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 | | `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 |
| `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 | | `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 |
| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.20**,2026-10-13) | | `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.21**,2026-10-13) |
## systems/ 五个循环 ## systems/ 五个循环
+7 -7
View File
@@ -2,7 +2,7 @@
> **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。 > **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。
> **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。 > **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。
> **状态**:**已定稿(v1.20)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。 > **状态**:**已定稿(v1.23)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **成文**:2026-10-13。 > **成文**:2026-10-13。
## 本文怎么用 ## 本文怎么用
@@ -103,8 +103,8 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 主菜单 | —— | 在役(迭代 33 卡②三入口化:进入存档/设置/退出游戏——入口拓扑=进入存档→选档界面→进档/新档→营地覆盖层→〔营地交互重做卡④〕营地场景图与生涯笔记本;2026-10-22 用户裁换代〔反哺 #13〕) | | 主菜单 | —— | 在役(迭代 33 卡②三入口化:进入存档/设置/退出游戏——入口拓扑=进入存档→选档界面→进档/新档→营地覆盖层→〔营地交互重做卡④〕营地场景图与生涯笔记本;2026-10-22 用户裁换代〔反哺 #13〕) |
| 开始覆盖层 | —— | 在役 | | 开始覆盖层 | —— | 在役 |
| 选档界面(3 存档位) | —— | 在役(迭代 33 卡③——空槽新档/有档进档/删档二次确认+摘要三项;2026-10-22 用户裁增行〔反哺 #8〕) | | 选档界面(3 存档位) | —— | 在役(迭代 33 卡③——空槽新档/有档进档/删档二次确认+摘要三项;2026-10-22 用户裁增行〔反哺 #8〕) |
| 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](ux/camp.md) | 在役(场景图与七物件点选随营地交互重做列入 Alpha,见下行) | | 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](ux/camp.md) | 在役(迭代 47 卡④⑤ 落地——场景图+七物件点选+Esc 浮窗与顶栏,2026-11-01 合回) |
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md)+[需求澄清](../../reports/需求澄清-营地交互重做-2026-10-18.md) | 部分在役(卡①②③ 已随迭代 33 实装合回,2026-10-22;卡④营地场景图 2026-10-21 用户令退回美术设计段,美术生成方案提案 2026-10-22 成文,余三个用户侧前置未销号:像素规格执行(Q6 甲过渡口径已裁)、点选反馈设计稿、参照物;卡⑤ Esc 浮窗随卡④ 代码波同行、未编排。2026-10-21 用户裁:五卡整体列入 Alpha。出口=五卡各自验收标准通过+营地屏用户目检通过) | | 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md) | 在役(五卡全部落地——卡①②③ 迭代 33 批合回、卡④⑤ 迭代 47 批合回,卡⑥ 句柄子容器为同批前置基建卡。设计过程件已按吸收即死退役,沿革查 git 提交史。2026-10-21 用户裁:五卡整体列入 Alpha。出口=五卡各自验收标准通过+营地屏用户目检〔登记体验验证清单 ㊺〕) |
| 生涯页 | [生涯](systems/跨局循环/生涯.md) | 在役(期一) | | 生涯页 | [生涯](systems/跨局循环/生涯.md) | 在役(期一) |
| 关卡地图屏 | [level-map](ux/level-map.md) | 在役 | | 关卡地图屏 | [level-map](ux/level-map.md) | 在役 |
| 战斗屏 | [battle](ux/battle.md) | 在役 | | 战斗屏 | [battle](ux/battle.md) | 在役 |
@@ -135,7 +135,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 机器可读的呈现断言(对比度 / 结构) | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 | | 机器可读的呈现断言(对比度 / 结构) | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 分辨率两档(1280×800 / 1920×1080) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役 | | 分辨率两档(1280×800 / 1920×1080) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役 |
| 绘制调用数达标 | —— | 在役 | | 绘制调用数达标 | —— | 在役 |
| 真人试玩轮次 | [体验验证清单](../../体验验证清单.md) | 在役(义务维持;当前轮次未清:㉚/㉛ 未核销,迭代 34 三条目检项待登记) | | 真人试玩轮次 | [体验验证清单](../../体验验证清单.md) | 在役(义务维持;当前轮次未清=㉚–㊸ 各条待用户核销——迭代 32 起各批实机目检项登记不阻塞,迭代 42 无玩家可见改动未登记) |
--- ---
@@ -147,7 +147,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 | | 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 |
| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史,复核转正归战斗一屏样张) | | 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史,复核转正归战斗一屏样张) |
| 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.md) | 待做(开工前置=先做素材轴/UI 轴对照截图定性,再按定轴修复;旧卡 v1.4 存 reports/ 作素材、非已写卡状态,重启须重新立卡;出口=用户实机目检第二、三关不再判「看着费劲」) | | 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.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/character-design.md) | 在役 | | 主角立绘三态 | [角色设计](../art/character-design.md) | 在役 |
| 状态图标 | [美术圣经](../art/art-bible.md) | 在役 | | 状态图标 | [美术圣经](../art/art-bible.md) | 在役 |
@@ -157,7 +157,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 站桩帧动画 | [待办清单](../../待办清单.md) | 待做(列入 Alpha 定版前置清单:定版前完成最小版,路线预选拆件驱动;未完成则顺延 Beta;不占功能迭代) | | 站桩帧动画 | [待办清单](../../待办清单.md) | 待做(列入 Alpha 定版前置清单:定版前完成最小版,路线预选拆件驱动;未完成则顺延 Beta;不占功能迭代) |
| 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) | | 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) |
| UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(2026-10-24 用户裁现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) | | 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) | 部分在役(光柱硬边已修=连续 alpha 渐变;未结半=九宫格切片错位——Alpha 内做最小修复:核对素材尺寸,必要时重出九宫格素材,全套素材重出排 Beta;出口=结算屏与图例屏错位消失,渲染闸+用户目检通过) |
| UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:①滑轨填充随迭代 25 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) | | UI 元素细节修(滑轨填充 / 图标垫块 / 留白 / 开发件外泄) | [待办清单](../../待办清单.md) | 在役(迭代 26 · 2026-10-14 入库:①滑轨填充随迭代 25 注销;余②图标垫块明度差降档/③商店+营地布局修/④诊断钮迁调试浮窗 全落地) |
@@ -190,7 +190,7 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 遗物式构筑协同 | [05-不做清单](05-不做清单.md) | | 遗物式构筑协同 | [05-不做清单](05-不做清单.md) |
| 战后自动回复 | [05-不做清单](05-不做清单.md) | | 战后自动回复 | [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」措辞系旧口径残留 | | 数值统一标定(M4 数值批) | 2026-10-09 里程碑立规:M4 = Beta 阶段、M3 未由用户明令关闭前不排期(用户 2026-10-07 令移出迭代 18;2026-10-16 确认此裁长期有效)——待办清单各条「已编入 Alpha 收官 M4」措辞系旧口径残留 |
| 连刷 / 换图资金跨战斗携带 | [05-不做清单](05-不做清单.md) | | 连刷 / 换图资金跨战斗携带 | [05-不做清单](05-不做清单.md) |
| 战斗节点地图类型差异化(更多地图主题) | 排 Beta 版本([playnote](../../playnote.md),2026-10-13 裁) | | 战斗节点地图类型差异化(更多地图主题) | 排 Beta 版本([playnote](../../playnote.md),2026-10-13 裁) |
+29 -3
View File
@@ -11,21 +11,23 @@
> >
> **兑付频率(先看这条,它是三条渠道的共同前提)**:奇物**不是打一场战斗给一份**。关内的普通战斗节点打完了什么也不给;一关之内奇物**只兑付一次**,触发点是**关底 Boss 被击败**——而按 [冒险结构](冒险结构.md) 的定义,打掉关底 Boss 恰好就等于"通过这一关",这两件事是同一个时刻,不是一条链上的两步。 > **兑付频率(先看这条,它是三条渠道的共同前提)**:奇物**不是打一场战斗给一份**。关内的普通战斗节点打完了什么也不给;一关之内奇物**只兑付一次**,触发点是**关底 Boss 被击败**——而按 [冒险结构](冒险结构.md) 的定义,打掉关底 Boss 恰好就等于"通过这一关",这两件事是同一个时刻,不是一条链上的两步。
> >
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地);现行为 v1.1。本文件是奇物的唯一定义文档。 > 状态:**已实现**(2026-10-04 双层 BD 实施批落地);现行为 v1.4。本文件是奇物的唯一定义文档。
> >
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `data/tables/feats.tres` 与 `data/tables/adventure.tres`。 > 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `data/tables/feats.tres` 与 `data/tables/adventure.tres`。
## 专长(重设计·基础框架 v1) ## 专长(重设计·基础框架 v1)
> 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已随迭代 20 专长实装批落地(2026-10-11):`feats.tres` 29 行=基础 27+迁入 2,行为与数据行 1:1。** > 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已实装:`feats.tres` 37 行=基础 27+迁入 2+规则豁免 1+换图系 7(换图系七件 2026-11-01 迭代 46 落表),行为与数据行 1:1。**
### 设计原则 ### 设计原则
1. **触发器句式**:每件专长的效果声明为「当 X 时 Y」(on / when / do 三字段,词表=效果引擎封闭集)。件的性格来自它挂在哪个事件上;combo 靠事件共享自然涌现——不设流派标签、不设档位机制。 1. **触发器句式**:每件专长的效果声明为「当 X 时 Y」(on / when / do 三字段,词表=效果引擎封闭集)。件的性格来自它挂在哪个事件上;combo 靠事件共享自然涌现——不设流派标签、不设档位机制。
2. **事件族组织**(设计侧分组工具,不上卡面):开格系 / 封印系 / 踩雷系 / 节奏系 / 战斗系五族。族名禁上玩家可见面(命名铁律)。 2. **事件族组织**(设计侧分组工具,不上卡面):开格系 / 封印系 / 踩雷系 / 节奏系 / 战斗系 / 换图系六族。族名禁上玩家可见面(命名铁律)。
3. **准入校验**:代价与收益须同账可核算——「纯代价件」禁止出生;契约件的代价必须一句话讲得清(契约不可怕,糊涂账才可怕)。 3. **准入校验**:代价与收益须同账可核算——「纯代价件」禁止出生;契约件的代价必须一句话讲得清(契约不可怕,糊涂账才可怕)。
4. **信息红线**:禁止任何效果让数字说谎(信息规则不豁免);揭示类效果只揭真格。 4. **信息红线**:禁止任何效果让数字说谎(信息规则不豁免);揭示类效果只揭真格。
5. **防兑换环**:互相转换的通道必须有损耗或每轮上限;单向通道优先。 5. **防兑换环**:互相转换的通道必须有损耗或每轮上限;单向通道优先。
6. **基座先行**:流派先确定基座专长——通过改变资源来源、动作成本或频率、动作结算、动作之间的关系,让原本偶尔用于止损的动作成为可以持续使用的主要打法。后续专长再围绕这个新行为提供输出、生存或资源收益;泛用数值增长不能代替基座。
7. **行为改变检验**:基座必须能回答「拿到前后操作有何不同、新行为靠什么资源持续、后续专长为何依附这个新行为」。只是同一操作后的结算数字变大,不算形成新流派。
> **落行注记(迭代 20 反哺写入,2026-10-12 追认⑤)**:「每第 N 次 / 积满触发」的数据行条件写 **N−1**——效果引擎分发时 when 先于 do 读计数器现值(预评估),故「满 N 触发」落行写 N−1(例:地质学家「满 4 种」写 eq 3、封印匠「每第 3 次」写 mod_eq ref 2、双管镐「每第 2 次」写 mod_eq ref 1)。行为仍为每第 N 次准点触发,单元测试钉住;新件落行照此口径。 > **落行注记(迭代 20 反哺写入,2026-10-12 追认⑤)**:「每第 N 次 / 积满触发」的数据行条件写 **N−1**——效果引擎分发时 when 先于 do 读计数器现值(预评估),故「满 N 触发」落行写 N−1(例:地质学家「满 4 种」写 eq 3、封印匠「每第 3 次」写 mod_eq ref 2、双管镐「每第 2 次」写 mod_eq ref 1)。行为仍为每第 N 次准点触发,单元测试钉住;新件落行照此口径。
@@ -95,6 +97,28 @@
|---|---|---| |---|---|---|
| 血税 | 本局:踩雷 −2 最大生命;标中 +1 攻击(**均下一战起生效**——战末汇入冒险加算区) | 双向都痛都爽,一句话讲得清;双向同账、本局累积、冒险收束清零。生命上限/攻击系属战界重建属性,即时改会被重建冲掉,故滞后一战兑付(2026-10-12 追认⑨回写;卡面 desc 同步补「下一战起生效」) | | 血税 | 本局:踩雷 −2 最大生命;标中 +1 攻击(**均下一战起生效**——战末汇入冒险加算区) | 双向都痛都爽,一句话讲得清;双向同账、本局累积、冒险收束清零。生命上限/攻击系属战界重建属性,即时改会被重建冲掉,故滞后一战兑付(2026-10-12 追认⑨回写;卡面 desc 同步补「下一战起生效」) |
### 换图系(7 件,已实装——2026-11-01 迭代 46 落表 `feats.tres`,稀有度 14/13/10/0、每件持有上限 1)
换图系把「弃图刷新」从坏图逃生改成主动构筑行为。两件基座分别解决 RAP 的来源与价格;后续专长让成功刷新产生输出、生存或 GP 资源收益。七件不要求一次拿齐,不设强制先拿基座、集件标签或集件奖励。
| 层 | 代号 | 规则 | 行为职责 |
|---|---|---|---|
| 基座 | 矿脉储能 | 每次付费挖格被受理后,额外获得 RAP;安全格与危险格同口径 | 持续挖图也能准备刷新,不再必须少挖停手 |
| 基座 | 省力周转 | 弃图刷新的 RAP 成本减半 | 同一份储备支持更多刷新 |
| 输出 | 崩壁冲击 | 成功刷新后,在本轮战斗段末对全体存活敌人造成一次固定伤害 | 刷新成为直接进攻行为 |
| 输出 | 蓄力周转 | 成功刷新攒层,最多 3 层;本轮下一次主角攻击消费全部层数后清零,战斗结束不跨战保留 | 连续换图蓄成一次单点爆发 |
| 生存 | 石层缓冲 | 本轮至少成功刷新一次后,主角每次受到伤害固定减少 2 点;不叠加 | 刷新形成当轮全局防御姿态 |
| 生存 | 坑道小憩 | 每次成功刷新累计 1 点恢复,最多 3 点;本轮授予段合计回复生命,封顶生命上限 | 放弃当前图换取恢复 |
| 资源 | 矿道红利 | 每次成功刷新固定获得 1 GP | 刷新动作自身带少量GP收益 |
共同规则:
- **双基座叠加**:矿脉储能与省力周转可以同时持有,产 RAP 与成本减半分别生效;每轮刷新次数上限继续限频,不设互斥规则。
- **成功才触发**:后续五件只认成功换图;刷新被拒绝时不产生事实,也不给输出、生存或资源收益。同一轮多次成功刷新按发生顺序逐次登记。
- **轮末兑付**:刷新不新增结算段。崩壁冲击走本轮战斗段末结账,石层缓冲在每次主角受伤时消费,坑道小憩与矿道红利走本轮授予段兑付。
- **全局受伤面**:石层缓冲覆盖敌方攻击、危险格、错误封印资源格与地形造成的主角伤害;临时生命值不在本流派设计范围内。
- **基线不变**:没有这些专长时,RAP 来源、刷新成本、清图奖励与伤害结果保持原规则。数值与稀有度归 `feats.tres`(已落表——稀有度档 14/13/10/0、每件持有上限 1;数值全 provisional 归调参批)。
### 迁入登记(秘宝域迁出件;实装批落地后并入件表) ### 迁入登记(秘宝域迁出件;实装批落地后并入件表)
| 代号沿用 | 目标族 | on / when / do 要点 | 备注 | | 代号沿用 | 目标族 | on / when / do 要点 | 备注 |
@@ -454,6 +478,8 @@
10. 一个渠道一旦确定要出某个分类,该分类的成员就都在它的池子里——"渠道出哪些分类"不受这条约束,那是渠道轴的定位。 10. 一个渠道一旦确定要出某个分类,该分类的成员就都在它的池子里——"渠道出哪些分类"不受这条约束,那是渠道轴的定位。
11. 凭证拿到即开,不进持有集,玩家无法囤积——选中或买下的同一时刻就开专长三选一。 11. 凭证拿到即开,不进持有集,玩家无法囤积——选中或买下的同一时刻就开专长三选一。
12. 凭证带有计步保底:连续 5 次三选一展示未出现则第 6 次必出;计数跨奖励节点与商店共享,刷新后的重新展示计入次数,限购用满后保底不再触发。 12. 凭证带有计步保底:连续 5 次三选一展示未出现则第 6 次必出;计数跨奖励节点与商店共享,刷新后的重新展示计入次数,限购用满后保底不再触发。
13. 被称为流派基座的专长必须能说明拿到前后的操作差异与新行为的持续资源来源;只有同一操作后的泛用数值增长,不得登记为基座。
14. 换图系两件基座同时持有时都生效;后续五件只在成功刷新后触发,无专长基线不变;石层缓冲覆盖所有主角受伤路径且同轮不叠加,矿道红利按成功刷新次数固定获得 GP。
> 实装批落地前,现役代码按旧 27 件专长目录与旧资源成员在同框架下运行;新成员口径(27 件基础件表 / 凭证件表)自实装批起验。 > 实装批落地前,现役代码按旧 27 件专长目录与旧资源成员在同框架下运行;新成员口径(27 件基础件表 / 凭证件表)自实装批起验。
@@ -1,7 +1,7 @@
# 地图战斗资源 # 地图战斗资源
> 一句话:格面资源的产出、极性字典与清图奖励封装 > 一句话:格面资源的产出、极性字典与清图奖励封装
> 状态:已定稿(自旧体系迁移);现行为 v1.1 > 状态:已定稿(自旧体系迁移);现行为 v1.4
## 设计目的 ## 设计目的
@@ -127,7 +127,7 @@
| defense 护甲 | ac_up 盾牌加护(AC 掷点阈值轴——2026-09-16 换装,不入池)——幅度/上限/权重见 `reward.tres` | off_guard 失衡(AC 掷点阈值轴负向——2026-09-18 换装,不入池、退出主角防御池)——幅度/上限见 `status_effects.tres` off_guard 行 | | defense 护甲 | ac_up 盾牌加护(AC 掷点阈值轴——2026-09-16 换装,不入池)——幅度/上限/权重见 `reward.tres` | off_guard 失衡(AC 掷点阈值轴负向——2026-09-18 换装,不入池、退出主角防御池)——幅度/上限见 `status_effects.tres` off_guard 行 |
| heal 回血 | heal_minor(满血零兑付照旧)——威力/权重见 `reward.tres` | heal_backlash 直伤:战斗结算直接扣血,不经增益状态——威力见 `reward.tres` | | heal 回血 | heal_minor(满血零兑付照旧)——威力/权重见 `reward.tres` | heal_backlash 直伤:战斗结算直接扣血,不经增益状态——威力见 `reward.tres` |
| blast 群伤 | spark_aoe 全体存活敌各吃一笔——威力/权重见 `reward.tres` | enemy_alert 警觉:敌方全体护甲等级 +1/层(`status_effects.tres` alert 行)——见 `reward.tres` | | blast 群伤 | spark_aoe 全体存活敌各吃一笔——威力/权重见 `reward.tres` | enemy_alert 警觉:敌方全体护甲等级 +1/层(`status_effects.tres` alert 行)——见 `reward.tres` |
| weaken 削弱 | weaken(敌方输出池;同名重复授予只刷新剩余次数——2026-09-18 续时定案)——幅度见 `status_effects.tres` weaken 行 | sapped(主角输出池)——幅度见 `status_effects.tres` sapped 行 | | weaken 削弱 | weaken(敌方输出池;同名重复授予只加时长)——幅度见 `status_effects.tres` weaken 行 | sapped(主角输出池)——幅度见 `status_effects.tres` sapped 行 |
| action 行动点 | ap_up:下轮行动点 +1——见 `reward.tres` | ap_drain 迟滞:下轮行动点 −1——见 `reward.tres` | | action 行动点 | ap_up:下轮行动点 +1——见 `reward.tres` | ap_drain 迟滞:下轮行动点 −1——见 `reward.tres` |
| gp 财货 | gp_gain GP 袋:战斗内 GP 缓冲 +power,战斗完成随 GP 掉落统一入钱包账本(兑付编排归[战斗结算](战斗结算.md)、账本归[冒险结构](../冒险循环/冒险结构.md))——威力/权重见 `reward.tres` | gp_loss 破财:战斗内 GP 缓冲 −power,weight=0 纯字典不参与摇号——见 `reward.tres` | | gp 财货 | gp_gain GP 袋:战斗内 GP 缓冲 +power,战斗完成随 GP 掉落统一入钱包账本(兑付编排归[战斗结算](战斗结算.md)、账本归[冒险结构](../冒险循环/冒险结构.md))——威力/权重见 `reward.tres` | gp_loss 破财:战斗内 GP 缓冲 −power,weight=0 纯字典不参与摇号——见 `reward.tres` |
| pierce 穿刺 | pierce_strike 穿刺弹:单目标克制直伤(attack_type=pierce 查克制矩阵,必中全额不掷点;目标 = 固定顺位最前存活敌)——威力/权重见 `reward.tres` | pierce_snare 穿刺陷阱:主角直伤,不经增益状态——威力见 `reward.tres` | | pierce 穿刺 | pierce_strike 穿刺弹:单目标克制直伤(attack_type=pierce 查克制矩阵,必中全额不掷点;目标 = 固定顺位最前存活敌)——威力/权重见 `reward.tres` | pierce_snare 穿刺陷阱:主角直伤,不经增益状态——威力见 `reward.tres` |
@@ -147,7 +147,7 @@
### 清图奖励对象:整图封装,可配置可替换 ### 清图奖励对象:整图封装,可配置可替换
清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格开完)时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。 清图奖励不属于任何一格的格面资源——它是作用域为整张板的独立奖励对象:把一张图的雷全部清完(全部安全格开完)时结算发的那一份。装板摇号时与格子资源同批判尾摇定身份;清图批兑付恰好一次;完美封印(零误标且至少一次封印标中)追加第二份同身份兑付。清图奖励只在清图批兑付:弃图刷新不触发清图事件,换来的新图不发清图奖励;刷新专长的固定 GP 收益走战斗内缓冲入账,不属清图奖励封装。
它是可配置可替换的封装:数据面上是清图池里的一行(现行默认槽 clear_default),换行即换内容,机制零改动。按图类型差异化已留结构位——`clear_<图类型>` 命名空间预留,不同地图类型可以配不同的清图奖励;词表现行 = {dungeon, underdark, deep_underdark}(2026-09-19 定稿两值、第三关内容批 2026-09-20 增 deep_underdark,玩家侧「地图类型」),差异化内容启用随内容批。 它是可配置可替换的封装:数据面上是清图池里的一行(现行默认槽 clear_default),换行即换内容,机制零改动。按图类型差异化已留结构位——`clear_<图类型>` 命名空间预留,不同地图类型可以配不同的清图奖励;词表现行 = {dungeon, underdark, deep_underdark}(2026-09-19 定稿两值、第三关内容批 2026-09-20 增 deep_underdark,玩家侧「地图类型」),差异化内容启用随内容批。
+18 -18
View File
@@ -1,13 +1,13 @@
# 增益状态 # 增益状态
> 一句话:增益/减益的状态池、叠层与衰减 > 一句话:增益/减益的状态池、叠层与衰减
> 状态:已定稿(自旧体系迁移);现行为 v1.2 > 状态:已定稿(自旧体系迁移);现行为 v1.3
## 设计目的 ## 设计目的
战斗里有一类效果不在当场一次性用掉,而要管上几次结算:挖开安全格拿到的主角增益、封印标中带来的怪物削弱、踩雷喂给怪物的增强(警觉——blast 型负向)、标错封印招来的负向兑付,都是这类。本文把它们统称"状态":发给主角的有益效果叫增益(失衡与乏力是例外——它们是发给主角的减益),发给敌人的负面效果叫减益;敌方侧的增强同样是状态——几个方向共用同一个状态池。 战斗里有一类效果不在当场一次性用掉,而要管上几次结算:挖开安全格拿到的主角增益、封印标中带来的怪物削弱、踩雷喂给怪物的增强(警觉——blast 型负向)、标错封印招来的负向兑付,都是这类。本文把它们统称"状态":发给主角的有益效果叫增益(失衡与乏力是例外——它们是发给主角的减益),发给敌人的负面效果叫减益;敌方侧的增强同样是状态——几个方向共用同一个状态池。
增益状态就是这一个状态池:它收下全部状态,用同一套规矩管理——发入、合并(同一种效果重复拿到时按表行策略处理:叠层加一,或只刷新剩余次数——读 `status_effects.tres` 逐行 stack_policy)、衰减(每次结算后剩余次数减一,减到零效果消失)、清空(战斗结束全部清除)。没有它,几类效果得各写一套计时逻辑,结算明细也无法逐行指回"这效果是我哪步棋带来的"。状态池管机制、不管数值从哪来:某个效果该多大,由各收益系统的表去定,状态池只照章入列、按次衰减。 增益状态就是这一个状态池:它收下全部状态,用同一套规矩管理——发入、合并(同一种效果重复拿到时按表行策略处理:叠层加一,或把剩余次数累加上本次授予的时长——读 `status_effects.tres` 逐行 stack_policy)、衰减(每次结算后剩余次数减一,减到零效果消失)、清空(战斗结束全部清除)。没有它,几类效果得各写一套计时逻辑,结算明细也无法逐行指回"这效果是我哪步棋带来的"。状态池管机制、不管数值从哪来:某个效果该多大,由各收益系统的表去定,状态池只照章入列、按次衰减。
## 系统范围 ## 系统范围
@@ -76,14 +76,14 @@
举证:本节为授予路径与登记规则,无计算。 举证:本节为授予路径与登记规则,无计算。
### 同名状态再拿到:叠层、刷新、并存 ### 同名状态再拿到:叠层、累加、并存
同一种状态还没消失又拿到一份,怎么合并由状态表每行的合并策略字段说了算,共三种。合并在授予那一刻由状态池完成;多个状态在结算里按什么先后起作用,是战斗结算的编排——两件事互不干扰。 同一种状态还没消失又拿到一份,怎么合并由状态表每行的合并策略字段说了算,共三种。合并在授予那一刻由状态池完成;多个状态在结算里按什么先后起作用,是战斗结算的编排——两件事互不干扰。
| 合并策略 | 重复授予时发生什么 | 说明 | | 合并策略 | 重复授予时发生什么 | 说明 |
|---|---|---| |---|---|---|
| 叠加 | 层数加一;剩余结算次数不动——只有首次授予定钟 | 现行内容行多数用叠加(逐行合并策略读 `status_effects.tres`) | | 叠加 | 层数加一;剩余结算次数不动——只有首次授予定钟 | 现行内容行多数用叠加(逐行合并策略读 `status_effects.tres`) |
| 刷新 | 剩余结算次数重置为该实例首次授予时的初始值,层数不变 | 刷新类内容行已就位——削弱行 weaken(2026-09-18 续时定案)、增益技行 empower / fortify、**主角增益行 attack_up / ac_up(2026-09-20 用户拍板「只续时不叠层」,全状态表增益/减益两侧合并语义就此统一为:主角可重复获取的增益与减益一律续时、不叠层)**;逐行读 `status_effects.tres` | | 累加 | 剩余结算次数加上本次授予的时长——技能施放行带覆盖时长时按覆盖值加;层数不变 | 现行内容行六行用累加(削弱行 weaken、增益技行 empower / fortify、主角增益行 attack_up / ac_up);重复拿一次就多攒一段时长,同一轮拿几份攒几段,停手后按剩余次数逐轮耗完;逐行读 `status_effects.tres` |
| 并存 | 新建一份独立实例,两份各算各的 | 同上 | | 并存 | 新建一份独立实例,两份各算各的 | 同上 |
并存分支没有满层上限,但状态池规模天然有界:实例数的上界就是受理过的授予数——多受理一份就多一份实例,多少份在授予那一刻就完全确定,不会无限膨胀。 并存分支没有满层上限,但状态池规模天然有界:实例数的上界就是受理过的授予数——多受理一份就多一份实例,多少份在授予那一刻就完全确定,不会无限膨胀。
@@ -110,7 +110,7 @@
### 剩余次数怎么倒数:按结算次数衰减 ### 剩余次数怎么倒数:按结算次数衰减
每个状态实例自带一个倒数计数——剩余结算次数(本文说的"衰减"就是它逐次递减、归零消失)。它不承诺"活几轮",而是数"经历过几次轮末结算":每轮结算先让状态起作用,随后计数减一,减到零状态即被移除。持续 1 的状态就是"一次性":在授予它的那轮结算里生效一次,轮末消失。MVP 时期六行内容均为一次性(削弱后改续时——见 git 提交史;2026-09-20 起主角增益两行也走续时、duration 表值仍为 1——重复拿等于重新获得一次当轮增益);跨轮持续的状态,寿命由表行初始值表达,给几就活几次结算。 每个状态实例自带一个倒数计数——剩余结算次数(本文说的"衰减"就是它逐次递减、归零消失)。它不承诺"活几轮",而是数"经历过几次轮末结算":每轮结算先让状态起作用,随后计数减一,减到零状态即被移除。持续 1 的状态就是"一次性":在授予它的那轮结算里生效一次,轮末消失。现行累加行里主角增益两行持续 1、削弱与敌方增益技行持续 2——累加语义下重复拿一次就按本次授予时长多攒一段,同一轮拿几份攒几段;跨轮持续的状态,寿命由表行初始值表达,给几就活几次结算。
``` ```
一轮结算:查询生效清单 → 逐行兑付(层数 × 行值)→ 轮末衰减(全部实例剩余次数 −1)→ 归零实例移除 一轮结算:查询生效清单 → 逐行兑付(层数 × 行值)→ 轮末衰减(全部实例剩余次数 −1)→ 归零实例移除
@@ -140,8 +140,8 @@
| 例子 | 状态池输出 | 消费侧读法 | | 例子 | 状态池输出 | 消费侧读法 |
|---|---|---| |---|---|---|
| 暴走叠两层 | +(2 × enrage 行幅度)(读表;越出单行取值域,原样输出) | 压在天花板 +100% 上:敌方输出乘区封顶 ×2(削弱已改续时——"叠两层压地板 −100% 归零"的读法随 2026-09-18 结构修复批退役) | | 暴走叠两层 | +(2 × enrage 行幅度)(读表;越出单行取值域,原样输出) | 压在天花板 +100% 上:敌方输出乘区封顶 ×2(削弱非叠层行——"叠两层压地板 −100% 归零"的读法不成立) |
| 主角盾牌加护再拿一份 | 层数恒 1、剩余结算次数重置(refresh——2026-09-20 主角增益续时批) | 不经任何池:掷点判定阈值 = 主角基准(读 hero 表)+ 层数(现行 +1——上限 2 层叠读取法退役,局内可达 AC 上限 11) | | 主角盾牌加护再拿一份 | 层数恒 1、剩余结算次数累加本次授予的时长 | 不经任何池:掷点判定阈值 = 主角基准(读 hero 表)+ 层数(现行 +1——上限 2 层叠读取法退役,局内可达 AC 上限 11) |
状态池还有一条快照纪律:实例的幅度、初始次数、量纲在授予那一刻从表行拷贝。战斗中配置表即使被重载,已授予实例不受任何影响,只影响后续授予。 状态池还有一条快照纪律:实例的幅度、初始次数、量纲在授予那一刻从表行拷贝。战斗中配置表即使被重载,已授予实例不受任何影响,只影响后续授予。
@@ -162,15 +162,15 @@
| 状态 | 效果一句话 | 发给谁 | 幅度(现行值) | 时限 / 合并 | 来源类别 | | 状态 | 效果一句话 | 发给谁 | 幅度(现行值) | 时限 / 合并 | 来源类别 |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| attack_up 增伤 | 主角攻击提升 | 主角 | 见 `data/tables/status_effects.tres` attack_up 行 | 时限/上限读表 / 刷新(2026-09-20 续时批 stack→refresh,层数恒 1) | 安全格 | | attack_up 增伤 | 主角攻击提升 | 主角 | 见 `data/tables/status_effects.tres` attack_up 行 | 时限/上限读表 / 累加(层数恒 1) | 安全格 |
| ac_up 盾牌加护 | 主角护甲等级提升(掷点折扣判定阈值——AC 轴,不入池) | 主角 | 见 `status_effects.tres` ac_up 行(2026-09-16 换装,原 defense_up 行已退役) | 时限/上限读表 / 刷新(2026-09-20 续时批 stack→refresh,层数恒 1) | 安全格 | | ac_up 盾牌加护 | 主角护甲等级提升(掷点折扣判定阈值——AC 轴,不入池) | 主角 | 见 `status_effects.tres` ac_up 行(2026-09-16 换装,原 defense_up 行已退役) | 时限/上限读表 / 累加(层数恒 1) | 安全格 |
| alert 警觉 | 敌方护甲等级 +1/层 | 敌方 | 见 `status_effects.tres` alert 行 | 1 结算 / 叠加读表 | 踩雷 / 标空封印(blast 型负向) | | alert 警觉 | 敌方护甲等级 +1/层 | 敌方 | 见 `status_effects.tres` alert 行 | 1 结算 / 叠加读表 | 踩雷 / 标空封印(blast 型负向) |
| weaken 削弱 | 敌方攻击被削弱(同名重复标中只刷新剩余次数、层数恒 1) | 敌方 | 见 `status_effects.tres` weaken 行(取值域 [60,85],取负生效) | 时限读表 / 刷新(2026-09-18 续时定案) | 封印标中 | | weaken 削弱 | 敌方攻击被削弱(同名重复标中只加时长、层数恒 1) | 敌方 | 见 `status_effects.tres` weaken 行(取值域 [60,85],取负生效) | 时限读表 / 累加 | 封印标中 |
| enrage 暴走 | 敌方攻击提升 | 敌方 | 见 `status_effects.tres` enrage 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(attack 型负向) | | enrage 暴走 | 敌方攻击提升 | 敌方 | 见 `status_effects.tres` enrage 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(attack 型负向) |
| off_guard 失衡 | 主角护甲等级降低(AC 轴——掷点判定阈值下调、更易被命中;2026-09-18 换装,原百分比承伤池已退) | 主角 | 见 `status_effects.tres` off_guard 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(defense 型负向)/ 敌方妨害技(2026-09-19 新效应词汇批——蛛网束缚载体,duration 由技能行覆盖) | | off_guard 失衡 | 主角护甲等级降低(AC 轴——掷点判定阈值下调、更易被命中;2026-09-18 换装,原百分比承伤池已退) | 主角 | 见 `status_effects.tres` off_guard 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(defense 型负向)/ 敌方妨害技(2026-09-19 新效应词汇批——蛛网束缚载体,duration 由技能行覆盖) |
| sapped 乏力 | 主角输出降低(weaken 型负向,主角输出池) | 主角 | 见 `status_effects.tres` sapped 行 | 时限/上限读表 / 叠加 | 危险格负向 / 标空封印(2026-09-15 格面资源批)/ 敌方妨害技(大地脉动——L3 Boss 石君王独占,2026-09-20 第三关内容批) | | sapped 乏力 | 主角输出降低(weaken 型负向,主角输出池) | 主角 | 见 `status_effects.tres` sapped 行 | 时限/上限读表 / 叠加 | 危险格负向 / 标空封印(2026-09-15 格面资源批)/ 敌方妨害技(大地脉动——L3 Boss 石君王独占,2026-09-20 第三关内容批) |
| empower 蓄力 | 敌方输出倍率增益(敌方输出池——蓄力技载体;幅度读表) | 敌方 | 见 `status_effects.tres` empower 行 | duration 读表 = 1 个有效增益拍 / 刷新 | 敌方技能施放(2026-09-16 敌人技能池批) | | empower 蓄力 | 敌方输出倍率增益(敌方输出池——蓄力技载体;幅度读表) | 敌方 | 见 `status_effects.tres` empower 行 | duration 读表 = 1 个有效增益拍 / 累加 | 敌方技能施放(2026-09-16 敌人技能池批) |
| fortify 硬化 | 敌方防御提升(**敌方防御池**——硬化技载体;负值入池 = 主角攻击折半,2026-09-18 符号纠正——旧正值实际翻倍) | 敌方 | 见 `status_effects.tres` fortify 行 | duration 读表 = 1 个有效增益拍 / 刷新 | 敌方技能施放(2026-09-16 敌人技能池批) | | fortify 硬化 | 敌方防御提升(**敌方防御池**——硬化技载体;负值入池 = 主角攻击折半,2026-09-18 符号纠正——旧正值实际翻倍) | 敌方 | 见 `status_effects.tres` fortify 行 | duration 读表 = 1 个有效增益拍 / 累加 | 敌方技能施放(2026-09-16 敌人技能池批) |
empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(技能行 buff_ref 引用,施放时长由技能行覆盖——技能冷却与增益持续是两个计时器):两行 duration 表值(读表)的口径是"先兑付后衰减含施放当轮"——施放发生在第 N 结算战斗段,存活到第 N+1 结算战斗段才兑付"下一拍",即"持续 1 个有效结算"(proposal 语义的状态池落地)。敌方防御池是第四个乘区池:敌方作为防守方的百分比修正池,消费面 = [战斗结算](战斗结算.md)主角攻击的池乘区——四池对称(敌打主角 = 主角防御池 × 敌方输出池,主角打敌 = 主角输出池 × 敌方防御池),状态池语义零延伸:仍是同构实例、side-wide 加算、不封顶不保底。 empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(技能行 buff_ref 引用,施放时长由技能行覆盖——技能冷却与增益持续是两个计时器):两行 duration 表值(读表)的口径是"先兑付后衰减含施放当轮"——施放发生在第 N 结算战斗段,存活到第 N+1 结算战斗段才兑付"下一拍",即"持续 1 个有效结算"(proposal 语义的状态池落地)。敌方防御池是第四个乘区池:敌方作为防守方的百分比修正池,消费面 = [战斗结算](战斗结算.md)主角攻击的池乘区——四池对称(敌打主角 = 主角防御池 × 敌方输出池,主角打敌 = 主角输出池 × 敌方防御池),状态池语义零延伸:仍是同构实例、side-wide 加算、不封顶不保底。
@@ -195,7 +195,7 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
1. 战斗结算通知战斗开始:状态池初始化,空池进入可用状态。 1. 战斗结算通知战斗开始:状态池初始化,空池进入可用状态。
2. 轮末结算时,战斗结算把这一轮的扫雷事实兑换成授予:挖开安全格给主角上增益、封印标中削弱敌人、踩雷给敌方上增强、标空封印触发反噬——授予入口是唯一进货口。 2. 轮末结算时,战斗结算把这一轮的扫雷事实兑换成授予:挖开安全格给主角上增益、封印标中削弱敌人、踩雷给敌方上增强、标空封印触发反噬——授予入口是唯一进货口。
3. 状态池按表行合并策略受理(新建 / 叠一层 / 刷新),实例立即生效并携带归因记录。 3. 状态池按表行合并策略受理(新建 / 叠一层 / 累加时长),实例立即生效并携带归因记录。
4. 战斗结算查询生效清单,逐行兑付进结算公式(有效值 = 层数 × 行值)。 4. 战斗结算查询生效清单,逐行兑付进结算公式(有效值 = 层数 × 行值)。
5. 兑付完做轮末衰减:全部实例剩余次数减一,归零移除——先兑付、后衰减。 5. 兑付完做轮末衰减:全部实例剩余次数减一,归零移除——先兑付、后衰减。
6. 战斗外授予、未知状态、满层授予一律被拒且状态池不变;满层的"奖励未生效"行归战斗结算的结算明细。 6. 战斗外授予、未知状态、满层授予一律被拒且状态池不变;满层的"奖励未生效"行归战斗结算的结算明细。
@@ -207,7 +207,7 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
| 优先级 | 内容 | 后置原因 | | 优先级 | 内容 | 后置原因 |
|---|---|---| |---|---|---|
| P1 | 刷新、并存两类合并策略的后续内容行 | 语义已定义、状态池已承载;刷新类已有内容(empower/fortify 2026-09-16 敌人技能池批、weaken 2026-09-18 续时定案),并存仍空,首个并存内容行上线时顺带补验收 | | P1 | 累加、并存两类合并策略的后续内容行 | 语义已定义、状态池已承载;累加类已有内容(weaken/empower/fortify/attack_up/ac_up),并存仍空,首个并存内容行上线时顺带补验收 |
| P1 | 清图奖励含增益的授予路径 | 来源类别已预留;内容与档位归[地图战斗资源](地图战斗资源.md)定稿 | | P1 | 清图奖励含增益的授予路径 | 来源类别已预留;内容与档位归[地图战斗资源](地图战斗资源.md)定稿 |
| P1 | 单体寻址扩展位(接口已定稿——五条语义见核心规则〈单体寻址扩展位〉节) | 实现随首个单体效果的内容批进游戏,真实入口验收随该批闭合 | | P1 | 单体寻址扩展位(接口已定稿——五条语义见核心规则〈单体寻址扩展位〉节) | 实现随首个单体效果的内容批进游戏,真实入口验收随该批闭合 |
| P2 | 驱散 / 延时 / 转移接口 | 状态一旦生效,玩家没有任何手段摘掉、延后或挪给别人——省掉一整类界面复杂度是明确的设计决定;要加属于新增接口的修订,不是调参 | | P2 | 驱散 / 延时 / 转移接口 | 状态一旦生效,玩家没有任何手段摘掉、延后或挪给别人——省掉一整类界面复杂度是明确的设计决定;要加属于新增接口的修订,不是调参 |
@@ -230,12 +230,12 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
| sapped 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 乏力;危险格负向 weaken 型,进主角输出修正池(2026-09-15 格面资源批补登记) | | sapped 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 乏力;危险格负向 weaken 型,进主角输出修正池(2026-09-15 格面资源批补登记) |
| empower 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 蓄力;敌方技能池增益技载体,进敌方输出修正池 | | empower 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 蓄力;敌方技能池增益技载体,进敌方输出修正池 |
| fortify 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 硬化;敌方技能池增益技载体,进**敌方防御池**(负值 = 减伤方向——2026-09-18 符号纠正) | | fortify 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 硬化;敌方技能池增益技载体,进**敌方防御池**(负值 = 减伤方向——2026-09-18 符号纠正) |
| 初始剩余次数 | 整数 [1,99] | duration_settlements | 一次性语义行逐行读表;续时行 = weaken(2026-09-18 定案,duration 2)+ empower/fortify(1 个有效增益拍——先兑付后衰减含施放当轮)+ **attack_up / ac_up(2026-09-20 主角增益续时批,duration 表值 1 维持——续时语义在 duration=1 下表现为重复拿重置当轮倒数;跨轮延续随将来 duration 拉长自然显形,数值归状态层重设计批)**;拉长 = 恢复跨轮延续、规划变简单 | | 初始剩余次数 | 整数 [1,99] | duration_settlements | 累加行 = weaken(duration 2)+ empower/fortify(表值 2 = 先兑付后衰减含施放当轮的 1 个有效增益拍)+ attack_up / ac_up(duration 1);累加语义下重复拿一次就按本次授予时长加一段;拉长表值 = 每次授予攒更多段、规划变简单 |
| 叠层上限 | 整数 [1,16] | max_stack | 仅叠加策略消费;刷新行层数恒 1——weaken / attack_up / ac_up 表值上限在续时语义下不消费(2026-09-18 / 2026-09-20),empower/fortify ×1(冷却闸下不可能自叠加) | | 叠层上限 | 整数 [1,16] | max_stack | 仅叠加策略消费;累加行层数恒 1、表值上限不消费,empower/fortify ×1(冷却闸下不可能自叠加) |
非调参的冻结项:三类合并策略语义、两种量纲语义、来源类别枚举、"先兑付后衰减"时序、拒绝原子性、零随机——改动走本文修订流程,不走调参。 非调参的冻结项:三类合并策略语义、两种量纲语义、来源类别枚举、"先兑付后衰减"时序、拒绝原子性、零随机——改动走本文修订流程,不走调参。
联动纪律:增伤与削弱是当量锚的两端;盾牌加护与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)的承伤预期联动(掷点折扣轴——AC 行值变更须复算生存链区间;2026-09-20 主角增益续时批后 ac_up 层数恒 1、局内可达 AC 上限 11,复算随调参批);削弱改续时后"单轮层数上限受[行动经济](../排雷循环/行动经济.md)行动点约束"的联动随之失效(2026-09-18 续时定案——同名重复标中只刷新剩余次数、层数恒 1;2026-09-20 起主角侧 attack_up / ac_up 同口径)。这些联动值禁止独立调参,修订须重跑锚对照并同步登记册。 联动纪律:增伤与削弱是当量锚的两端;盾牌加护与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)的承伤预期联动(掷点折扣轴——AC 行值变更须复算生存链区间;ac_up 层数恒 1、局内可达 AC 上限 11,复算随调参批);削弱与主角增益行同名重复授予只加时长、层数恒 1,"单轮层数上限受[行动经济](../排雷循环/行动经济.md)行动点约束"的联动不成立。这些联动值禁止独立调参,修订须重跑锚对照并同步登记册。
## 验收标准 ## 验收标准
@@ -243,7 +243,7 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
- 多向同构:主角侧增益与减益、敌方侧增强与削弱共用同一套受理、合并、倒数、清除语义,不存在第二套计时逻辑。 - 多向同构:主角侧增益与减益、敌方侧增强与削弱共用同一套受理、合并、倒数、清除语义,不存在第二套计时逻辑。
- 授予即生效:状态发入当轮结算即兑付第一次,无"待生效"中间态。 - 授予即生效:状态发入当轮结算即兑付第一次,无"待生效"中间态。
- 只登记不去重:同一事实被转化两次授予,登记两层——去重责任在战斗结算。 - 只登记不去重:同一事实被转化两次授予,登记两层——去重责任在战斗结算。
- 叠层不减钟:叠加策略行叠加只加层数,剩余次数保持首次授予定下的轨迹不变(刷新策略行则相反——层数不变、只重置剩余次数;两策略逐行读表)。 - 叠层不减钟:叠加策略行叠加只加层数,剩余次数保持首次授予定下的轨迹不变(累加策略行则相反——层数不变、剩余次数加上本次授予的时长;两策略逐行读表)。
- 满层拒绝原子且留痕:叠满后再授予返回失败、状态池逐字不变;结算明细多一行"奖励未生效"(含来源)。 - 满层拒绝原子且留痕:叠满后再授予返回失败、状态池逐字不变;结算明细多一行"奖励未生效"(含来源)。
- 先兑付后衰减:每轮结算恰做一次衰减,归零实例当轮移除;停手轮、弃图刷新轮照常衰减。 - 先兑付后衰减:每轮结算恰做一次衰减,归零实例当轮移除;停手轮、弃图刷新轮照常衰减。
- 合成不设上下限:状态池输出的有效值恒等于层数 × 行值,越出百分比域不封顶不保底;地板在战斗结算侧。 - 合成不设上下限:状态池输出的有效值恒等于层数 × 行值,越出百分比域不封顶不保底;地板在战斗结算侧。
@@ -1,7 +1,7 @@
# 战斗结算 # 战斗结算
> 一句话:轮末结算的编排与逐行兑现 > 一句话:轮末结算的编排与逐行兑现
> 状态:已定稿(自旧体系迁移);现行为 v1.2 > 状态:已定稿(自旧体系迁移);现行为 v1.5
## 设计目的 ## 设计目的
@@ -84,13 +84,13 @@
| 判定行 | 胜 / 负 / 继续各一行;终局判定行同时是胜负画面的数据锚 | | 判定行 | 胜 / 负 / 继续各一行;终局判定行同时是胜负画面的数据锚 |
| 高光行 | 完美封印成立时一行,只记实际有效兑付 | | 高光行 | 完美封印成立时一行,只记实际有效兑付 |
每行还携带公共字段:来源类别、来源格、轮次、显示名、数值,以及可空的高光标记(用于强调封印标中、清图奖励这类值得高亮的结果)。没生效也要留痕:两类"未生效"行如实记录合法例外,呈现时聚合计数,不逐行刷屏。 每行还携带公共字段:来源类别、来源格、轮次、显示名、数值,以及可空的高光标记(用于强调封印标中、清图奖励这类值得高亮的结果)。奖励直伤行的数值字段沿用 `effectiveness` 键名、值为整数点数——键名与量纲是数据导出与分析工具的兼容契约,改动须连动分析工具面。没生效也要留痕:两类"未生效"行如实记录合法例外,呈现时聚合计数,不逐行刷屏。
任何一行找不到来源即断言失败——宁可返回错误,也不让"合并到看不出来源"的呈现混过数据层。但有些行天然没有棋盘来源,断言为此留了两层豁免,豁免集合是封闭清单,改动同样走本文修订。 任何一行找不到来源即断言失败——宁可返回错误,也不让"合并到看不出来源"的呈现混过数据层。但有些行天然没有棋盘来源,断言为此留了两层豁免,豁免集合是封闭清单,改动同样走本文修订。
第一层按行型豁免两类:衰减行(到期移除本就是段级动作,不属于任何一格)、判定行(它是胜负出口的结论,不是棋盘事实)。这两类连来源类别都不必填。 第一层按行型豁免两类:衰减行(到期移除本就是段级动作,不属于任何一格)、判定行(它是胜负出口的结论,不是棋盘事实)。这两类连来源类别都不必填。
第二层按来源类别豁免,封闭清单五类:主角攻击行(来源是主角单位本身)、敌方行动行(来源是当时动手的那个敌人单位)、清图奖励行(来源是"这张板被清掉"这一整板事实)、专长揭示掷格行(落空行没有真实棋盘格可指)、专长 GP 入账行(来源是钱包面而非棋盘面)——它们都不指向某个棋盘格,来源格校验因此跳过。 第二层按来源类别豁免,封闭清单六类:主角攻击行(来源是主角单位本身)、敌方行动行(来源是当时动手的那个敌人单位)、清图奖励行(来源是"这张板被清掉"这一整板事实)、专长揭示掷格行(落空行没有真实棋盘格可指)、专长 GP 入账行(来源是钱包面而非棋盘面)、专长刷新效果行(来源是刷新专长与被弃旧图,不指向单一格)——它们都不指向某个棋盘格,来源格校验因此跳过。
豁免不等于免检:按行型豁免的行仍要带齐公共字段、守住各自的语义约束(如衰减行的数值必须是本次移除的读数),按来源类别豁免的行仍必须写明来源类别,其余行必须指回一个真实存在的棋盘格。豁免只是免掉"来源格必须有效"这一条校验,不是免掉整份行协议。 豁免不等于免检:按行型豁免的行仍要带齐公共字段、守住各自的语义约束(如衰减行的数值必须是本次移除的读数),按来源类别豁免的行仍必须写明来源类别,其余行必须指回一个真实存在的棋盘格。豁免只是免掉"来源格必须有效"这一条校验,不是免掉整份行协议。
@@ -266,6 +266,24 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
挖掘期刷新是玩家在挖掘中途主动换图,经[行动经济](../排雷循环/行动经济.md)扣费:扣费成功后按"旧板退役、生成新板、装板、资源预分配恰一次、变异布置"编排,本轮不打断——没有结算段、没有轮次结束。储备不足被拒是玩家决策的合法结果,不是编排错误。刷新图不发清图奖励,完美封印计数随装板重置。 挖掘期刷新是玩家在挖掘中途主动换图,经[行动经济](../排雷循环/行动经济.md)扣费:扣费成功后按"旧板退役、生成新板、装板、资源预分配恰一次、变异布置"编排,本轮不打断——没有结算段、没有轮次结束。储备不足被拒是玩家决策的合法结果,不是编排错误。刷新图不发清图奖励,完美封印计数随装板重置。
### 换图系专长:刷新事实与本轮兑付(已实装——2026-11-01 迭代 46)
持有换图系后续专长时,成功刷新除执行上述换图编排外,还产生一次 `board_refreshed` 事实——它是换图系效果兑付的统一事实挂点。事实只在新板装板成功后登记恰一次;刷新被拒绝时不产生事实,任何后续效果都不触发。同一轮多次成功刷新按发生顺序逐次登记,不合并成一次。
刷新只登记事实,不当场插入结算。各效果在既有五段与对应消费点兑付:
| 专长 | 兑付规则 |
|---|---|
| 崩壁冲击 | 每次刷新事实开一张全体敌人伤害单据,在本轮战斗段末经伤害控制器统一结账;目标按结账时的存活状态解析 |
| 蓄力周转 | 每次刷新事实累计一层,最多 3 层;本轮下一次主角攻击消费全部层数对应的攻击加成,随后清零;战斗结束不跨战保留 |
| 石层缓冲 | 每次刷新事实把当轮全局减伤状态置为生效;本轮主角每次受到伤害都固定减少 2 点,同一轮多次刷新不继续叠加 |
| 坑道小憩 | 每次刷新事实累计 1 点恢复量,最多 3 点;本轮授予段合计回复生命,封顶生命上限,随后清零;满血时记未生效行 |
| 矿道红利 | 每次刷新事实固定获得 1 GP,走战斗内 GP 缓冲并在授予段留痕;不按旧图探索进度折算,也不与清图奖励绑定 |
全局受伤事件统一:`damage_taken` 是主角受到伤害的唯一效果事件——敌方攻击、危险格、错误封印资源直伤与地形四条伤害路径统一经同一发射口发出、同一消费族消费,对该事件的全部既有订阅(减免、护甲修正、反击攒点、护盾吸收)对四条路径同等生效。石层缓冲订阅该事件并在现行伤害下限之前消费。临时生命值不在换图系流派设计范围内。
崩壁冲击若击杀最后敌人,仍由本轮判定段按既有顺序判胜,不新增挖掘期即时胜利出口。换图系效果使用扩展行留痕:崩壁冲击走伤害单据,石层缓冲在受伤消费行体现减免,坑道小憩与矿道红利分别走治疗与 GP 入账记录。刷新事实来源不指向单一棋盘格,使用明细流水的专长刷新效果来源豁免,不伪造来源格。
战斗开始按固定序列开张:关上配置热重载的闸门、初始化状态池、掷骰定敌方实际数量、生成敌人、装板并预分配资源恰一次、变异布置([地形变异](../排雷循环/地形变异.md)——棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置,消费 `variant_place` 封闭流)、发开轮通知并执行开轮摇号(摇定首轮各敌本拍动作)。上一关的大奖励(如血上限提升)经战斗开始入参的覆盖值通道生效,内容归[冒险结构](../冒险循环/冒险结构.md)。 战斗开始按固定序列开张:关上配置热重载的闸门、初始化状态池、掷骰定敌方实际数量、生成敌人、装板并预分配资源恰一次、变异布置([地形变异](../排雷循环/地形变异.md)——棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置,消费 `variant_place` 封闭流)、发开轮通知并执行开轮摇号(摇定首轮各敌本拍动作)。上一关的大奖励(如血上限提升)经战斗开始入参的覆盖值通道生效,内容归[冒险结构](../冒险循环/冒险结构.md)。
举证:本节为编排流程,无计算。 举证:本节为编排流程,无计算。
@@ -360,6 +378,10 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
- 无来源即炸:每行明细都能指回一个来源事实(棋盘格、单位、清图或段级结论);找不到来源即断言失败,行型与来源类别两层豁免清单见明细流水一节,豁免行仍受其它契约约束。 - 无来源即炸:每行明细都能指回一个来源事实(棋盘格、单位、清图或段级结论);找不到来源即断言失败,行型与来源类别两层豁免清单见明细流水一节,豁免行仍受其它契约约束。
- 行型封闭:流水行型只出现本文登记的基础 9 类与扩展行;行型词表改动必须走本文修订并同步呈现层行格式化,未登记行型禁止落行。 - 行型封闭:流水行型只出现本文登记的基础 9 类与扩展行;行型词表改动必须走本文修订并同步呈现层行格式化,未登记行型禁止落行。
- 伤害单据管线(2026-09-24):全部伤害(主角先手、敌方伤害技、棋盘克制直伤、群伤、反噬、地形变异)经伤害控制器单据管线兑现,兑付配方封闭五种;目标在结账时刻照规则解析;死亡事件由单位自行宣布——怪死其未结单据作废(零行、不进冷却不进保底计数)、主角死兑付终止;段界结账为现行口径、开单即结为回切结构位,两模式各自同种子同输入逐位复现。 - 伤害单据管线(2026-09-24):全部伤害(主角先手、敌方伤害技、棋盘克制直伤、群伤、反噬、地形变异)经伤害控制器单据管线兑现,兑付配方封闭五种;目标在结账时刻照规则解析;死亡事件由单位自行宣布——怪死其未结单据作废(零行、不进冷却不进保底计数)、主角死兑付终止;段界结账为现行口径、开单即结为回切结构位,两模式各自同种子同输入逐位复现。
- 换图系刷新事实:成功刷新保存旧图探索进度并登记一次事实,被拒绝时不登记;同一轮多次刷新按发生顺序逐次兑付,刷新本身不新增结算段或轮次结束。
- 换图系输出:崩壁冲击在本轮战斗段末经伤害控制器结算;蓄力周转受 3 层上限约束,本轮下一次主角攻击消费全部层数,战斗结束清空;群伤致敌清空时仍由本轮判定段判胜。
- 换图系生存与资源:石层缓冲经全局 `damage_taken` 对敌方攻击、危险格、错误封印与地形伤害各减 2,同轮不叠加;坑道小憩在授予段按累计值回血并封顶;矿道红利每次成功刷新固定入账 1 GP。
- 全局受伤事件:`damage_taken` 只在主角实际承伤消费点发射一次,所有伤害路径均经同一效果引擎减免面,禁止同一张单重复发射或只让部分伤害来源订阅生效。
- 条款3 对账(2026-10-05 判式换代批 T3——与行动经济 F1 条款3 重立同步):①聚合健康式 (1−ρ̂)·E_dig(u) − ρ̂·V_mine(u) ≥ 0 与 β 比值带 [1.2, 2.5] 判读逐 u 检算(清图支配性审计为常设重跑项,读数快照登记归 clause3 重立条);②封印溢价兑付正确:封印标中正向兑付 = 同型正向 ×(1+δ)(δ 读 `settlement.tres` flag_hit_premium,100=无溢价直通零扰动),三类形态全覆盖(flat power / percent 幅度 / gp 缓冲)、标空与挖格兑付不乘溢价、危险格与安全格同池结构零触碰。 - 条款3 对账(2026-10-05 判式换代批 T3——与行动经济 F1 条款3 重立同步):①聚合健康式 (1−ρ̂)·E_dig(u) − ρ̂·V_mine(u) ≥ 0 与 β 比值带 [1.2, 2.5] 判读逐 u 检算(清图支配性审计为常设重跑项,读数快照登记归 clause3 重立条);②封印溢价兑付正确:封印标中正向兑付 = 同型正向 ×(1+δ)(δ 读 `settlement.tres` flag_hit_premium,100=无溢价直通零扰动),三类形态全覆盖(flat power / percent 幅度 / gp 缓冲)、标空与挖格兑付不乘溢价、危险格与安全格同池结构零触碰。
## 待办 ## 待办
@@ -119,7 +119,7 @@
| trigger(触发条件) | 全部,可省 | {fact, op, value, latch} 四键——fact 与 op 取「触发条件」节封闭词表,value 类型与域随 fact,出域加载期拒绝;缺省无字段=无条件技,旧行为零变化(2026-10-26 情境条件触发器批立列、2026-10-27 迭代 39 实装落地;原池条目的血量百分比字段收编为本字段闩型首用例) | | trigger(触发条件) | 全部,可省 | {fact, op, value, latch} 四键——fact 与 op 取「触发条件」节封闭词表,value 类型与域随 fact,出域加载期拒绝;缺省无字段=无条件技,旧行为零变化(2026-10-26 情境条件触发器批立列、2026-10-27 迭代 39 实装落地;原池条目的血量百分比字段收编为本字段闩型首用例) |
| P1 字段位 | — | 命中段数、目标数——随对应后置批加列(触发条件位已由 trigger 立列收编——2026-10-26 立列、2026-10-27 实装;原"固定值分量"位已由 rider_mult 实装收编——2026-09-19;多段命中「命中段数」保留以防复活) | | P1 字段位 | — | 命中段数、目标数——随对应后置批加列(触发条件位已由 trigger 立列收编——2026-10-26 立列、2026-10-27 实装;原"固定值分量"位已由 rider_mult 实装收编——2026-09-19;多段命中「命中段数」保留以防复活) |
普通攻击也是一行技能行,两条域校验锁死它的特殊性:倍率恒 1、冷却恒 0——想配倍率或冷却的就不是普攻,是伤害技。技能伤害存倍率、不存固定值:伤害 = 施放者攻击基线 × 技能倍率——同一行"重击",不同敌种按各自攻击基线缩放 = 基线 A × 技能倍率 M(行值读 enemy.tres / skills.tres),深层强化行自动缩放,这是共享技能表成立的前提;**固定的绝对值伤害不满足该前提**(不随基线缩放、跨关稀释——Brotato 教训),"固定值伤害"的合法形态是**必中附加分量 rider_mult**(×攻击基线的必中小倍率,2026-09-19 实装)。增益技带效果引用与持续回合,百分比天然按施放者同池生效,无缩放问题;妨害技同构,落点由状态行 target_side 路由到主角。 普通攻击也是一行技能行,两条域校验锁死它的特殊性:倍率恒 1、冷却恒 0——想配倍率或冷却的就不是普攻,是伤害技。技能伤害存倍率、不存固定值:伤害 = 施放者攻击基线 × 技能倍率——同一行"重击",不同敌种按各自攻击基线缩放 = 基线 A × 技能倍率 M(行值读 enemy.tres / skills.tres),深层强化行自动缩放,这是共享技能表成立的前提;**固定的绝对值伤害不满足该前提**(不随基线缩放、跨关稀释——Brotato 教训),"固定值伤害"的合法形态是**必中附加分量 rider_mult**(×攻击基线的必中小倍率,2026-09-19 实装)。增益技带效果引用与持续回合,百分比天然按施放者同池生效,无缩放问题;妨害技同构,落点由状态行 target_side 路由到主角。技能伤害基线的取整:攻击基线 × 倍率按四舍五入、正好半格时一律向上取——全伤害管线唯一不用向下取整的点(半点上取保住低攻击档特殊技的辨识度:攻击 1 配 ×1.5 的突刺落到 2 而不是 1)。
池构成纪律(加载校验,违规拒绝入表):**每池恰一行普通攻击**(普攻冷却恒 0 → 可摇集合恒非空,"空池不可能"由结构保证,最坏情况怪物平砍等待——那是玩家的反打窗口而非死拍);**每池增益类技能 ≤1**(非 RPG,怪物连出不同增益显得呆);**每池妨害类技能 ≤1**(2026-09-19 新效应词汇批同构纪律);**每池至少一行不带触发条件的特殊技**(普攻不算——保底特殊技集合恒非空的结构保证,2026-10-26 触发条件批);池条目 skill_id 悬空致命;权重 > 0、冷却 ≥ 0。 池构成纪律(加载校验,违规拒绝入表):**每池恰一行普通攻击**(普攻冷却恒 0 → 可摇集合恒非空,"空池不可能"由结构保证,最坏情况怪物平砍等待——那是玩家的反打窗口而非死拍);**每池增益类技能 ≤1**(非 RPG,怪物连出不同增益显得呆);**每池妨害类技能 ≤1**(2026-09-19 新效应词汇批同构纪律);**每池至少一行不带触发条件的特殊技**(普攻不算——保底特殊技集合恒非空的结构保证,2026-10-26 触发条件批);池条目 skill_id 悬空致命;权重 > 0、冷却 ≥ 0。
@@ -1,7 +1,7 @@
# 行动经济 # 行动经济
> 一句话:每轮行动点怎么花、停手与刷新的代价 > 一句话:每轮行动点怎么花、停手与刷新的代价
> 状态:已定稿(自旧体系迁移);现行为 v1.1 > 状态:已定稿(自旧体系迁移);现行为 v1.4
## 设计目的 ## 设计目的
@@ -91,6 +91,19 @@
停手与刷新合起来构成这本账的基线节拍:满行动点一轮停手,正好攒够一次刷新。行动点修正队列会扰动该节拍的实际兑现(+1 轮可多挖一格),不变式本身不动;对账归曲线调参批。 停手与刷新合起来构成这本账的基线节拍:满行动点一轮停手,正好攒够一次刷新。行动点修正队列会扰动该节拍的实际兑现(+1 轮可多挖一格),不变式本身不动;对账归曲线调参批。
### 换图系专长对储备与刷新的改写(已实装——2026-11-01 迭代 46 落表 `feats.tres`)
换图系两件基座分别改写 RAP 的来源与刷新价格。它们只给持有专长的玩家增加规则,不改变无专长时的基线账本。
| 专长 | 规则 | 不变边界 |
|---|---|---|
| 矿脉储能 | 每次付费挖格被受理后,额外获得表值 RAP;安全格与危险格同口径 | 连锁展开只随首格的一次付费动作结算,不逐格追加;封印、移动、刷新不产 RAP |
| 省力周转 | 弃图刷新的 RAP 成本为基线的 50% | 只改价格;AP、轮次、每轮刷新上限与换图流程不变 |
两件基座可以同时持有并叠加:付费挖格照常产 RAP,刷新同时按半价扣费。两条规则作用在不同账户入口,不互相覆盖,也不设互斥。折后价向下取整,受理门槛判断与实际扣费使用同一个折后值,只改价格不改拒绝判据——刷新仍须储备达到折后门槛才受理;储备不足或超过每轮次数上限时拒绝,状态不变。RAP 仍在战斗结束清零,刷新本身不返 RAP。
无矿脉储能时,挖格不新增 RAP;无省力周转时,刷新成本仍读全局基值。后续输出、生存与资源专长如何使用成功刷新这个事实,归[战斗结算](../战斗循环/战斗结算.md)。
### 下轮行动点修正(格面资源八型批 2026-09-20) ### 下轮行动点修正(格面资源八型批 2026-09-20)
格面资源八型批(2026-09-20)新增 action 型格面资源:正向行 ap_up = 下轮行动点 +1,负向行 ap_drain(迟滞)= 下轮行动点 −1。修正不在授予当轮生效:结算期经行动经济的行动点修正队列登记(同轮多笔按净值累加),下一轮开始时(开轮通知)一次性兑现——兑现值 = 基线 ap_per_round + 排队净值,下限 0,兑现后队列清零;排队净值不跨轮保留。明细在结算流水记效果行(effect_id=ap_delta)。 格面资源八型批(2026-09-20)新增 action 型格面资源:正向行 ap_up = 下轮行动点 +1,负向行 ap_drain(迟滞)= 下轮行动点 −1。修正不在授予当轮生效:结算期经行动经济的行动点修正队列登记(同轮多笔按净值累加),下一轮开始时(开轮通知)一次性兑现——兑现值 = 基线 ap_per_round + 排队净值,下限 0,兑现后队列清零;排队净值不跨轮保留。明细在结算流水记效果行(effect_id=ap_delta)。
@@ -236,6 +249,8 @@
| move_ap_cost | int | 表值(冻结 0) | 移动免费——冻结,非调参项 | | move_ap_cost | int | 表值(冻结 0) | 移动免费——冻结,非调参项 |
| chain_ap_cost | int | 表值(冻结 0) | 连锁免费——冻结,非调参项 | | chain_ap_cost | int | 表值(冻结 0) | 连锁免费——冻结,非调参项 |
| refresh_cap_per_round | int | 表值(provisional,结构位预留) | 每轮刷新上限,开轮重置计数 | | refresh_cap_per_round | int | 表值(provisional,结构位预留) | 每轮刷新上限,开轮重置计数 |
| (跨表)矿脉储能产 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 件兑现值与全冒险次数上限;加成轴语义见「跨冒险行动点加成轴」节 | | (跨表)跨冒险行动点加成 | int [0,10] / int [0,99],冒险表 | adventure_table bonus_ap_flat / bonus_cap_ap(provisional) | 三选一 AP 件兑现值与全冒险次数上限;加成轴语义见「跨冒险行动点加成轴」节 |
字段名注记:2026-09-18 措辞完成批已全链对齐正式名——表字段与本文行名统一为 `refresh_cap_per_round`,刷新入口命名与表字段口径对齐(命名统一登记兑现,切片临时名零残留)。 字段名注记:2026-09-18 措辞完成批已全链对齐正式名——表字段与本文行名统一为 `refresh_cap_per_round`,刷新入口命名与表字段口径对齐(命名统一登记兑现,切片临时名零残留)。
@@ -250,6 +265,8 @@
- 仲裁唯一:最后 1 点行动点挖开最后一个安全格时,判清图(剩余清零 + 换图 + 清图奖励),不判耗尽;输出恰一条。 - 仲裁唯一:最后 1 点行动点挖开最后一个安全格时,判清图(剩余清零 + 换图 + 清图奖励),不判耗尽;输出恰一条。
- 停手 1:1:剩余 3 点停手,储备池加 3,下轮同图继续。 - 停手 1:1:剩余 3 点停手,储备池加 3,下轮同图继续。
- 刷新规则:储备不足时刷新被拒绝且状态不变;同一轮第 3 次刷新被拒绝;刷新不重置行动点、不发清图奖励、旧图上的封印印记失效。 - 刷新规则:储备不足时刷新被拒绝且状态不变;同一轮第 3 次刷新被拒绝;刷新不重置行动点、不发清图奖励、旧图上的封印印记失效。
- 矿脉储能:持有专长时,每次受理的付费挖格额外产一次 RAP,安全格与危险格同口径,连锁展开不逐格追加;无专长时挖格不新增 RAP。
- 省力周转与双基座:持有省力周转时按基线的 50% 扣费;与矿脉储能同时持有则两条规则都生效,刷新仍受储备门槛与每轮次数上限约束。
- 储备清零:战斗结束信号到达后储备池归 0,行动点进入休眠。 - 储备清零:战斗结束信号到达后储备池归 0,行动点进入休眠。
- 跨字段校验:任一动作成本配置超过每轮预算时,加载期致命中止。 - 跨字段校验:任一动作成本配置超过每轮预算时,加载期致命中止。
- 零随机:同一动作序列驱动两个独立行动点实例,轮次结束记录与储备轨迹逐项相同。 - 零随机:同一动作序列驱动两个独立行动点实例,轮次结束记录与储备轨迹逐项相同。
@@ -55,13 +55,13 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
三区各管一块: 三区各管一块:
| 区 | 位置与尺寸 | 管什么 | | 区 | 位置与几何 | 管什么 |
|---|---|---| |---|---|---|
| B 战斗画面 | 全宽 × 270px | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 | | B 战斗画面 | 全宽,高读布局表 `stage_h` | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 |
| C 角色信息 | 左下 240×530 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 | | C 角色信息 | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
| D 扫雷图 | 中下约 1036×530(原右下面板删除后横向加宽) | 行动点条与按钮组、棋盘本体、toast 槽 | | D 扫雷图 | 中下,宽 = 基准屏宽 − C 区宽 − 列间距(原右下面板删除后横向加宽),高同 C 区 | 行动点条与按钮组、棋盘本体、toast 槽 |
分区尺寸与组件落位的像素级裁决归 [battle.md](../../ux/battle.md);三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。 分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`(经 `ConfigDb.presentation.battle_layout` 读取);分区尺寸与组件落位的像素级裁决归 [battle.md](../../ux/battle.md),两份文档只写结构关系、不复写数值。三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。
**结论**:棋盘格边长由 [battle.md](../../ux/battle.md)「棋盘格尺寸公式」按棋盘区实测面积与板型算出;本文只判"塞不塞得进",不复制算式与算例。**分辨率档位(2026-09-30 迭代 9 三裁决案甲修订)**:两档=1280×800 基准档(本表全部定值的口径)+1920×1080 档=**全局等比**(stretch canvas_items+aspect keep——16:10 设计在 16:9 窗口为等比 1.35×+左右黑边各 ~96px);本节与各屏定值一律按 1280 基准档书写,1920 档由引擎等比承载、零重排。 **结论**:棋盘格边长由 [battle.md](../../ux/battle.md)「棋盘格尺寸公式」按棋盘区实测面积与板型算出;本文只判"塞不塞得进",不复制算式与算例。**分辨率档位(2026-09-30 迭代 9 三裁决案甲修订)**:两档=1280×800 基准档(本表全部定值的口径)+1920×1080 档=**全局等比**(stretch canvas_items+aspect keep——16:10 设计在 16:9 窗口为等比 1.35×+左右黑边各 ~96px);本节与各屏定值一律按 1280 基准档书写,1920 档由引擎等比承载、零重排。
@@ -74,9 +74,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 变量 | 含义 | 现行值 | | 变量 | 含义 | 现行值 |
|---|---|---| |---|---|---|
| N | 棋盘边长格数 | 读[棋盘生成](../排雷循环/棋盘生成.md)(现行四档;事实源 board_gen.tres) | | N | 棋盘边长格数 | 读[棋盘生成](../排雷循环/棋盘生成.md)(现行四档;事实源 board_gen.tres) |
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条 44px 得出,本表不复制现算值 | | 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条高(读 `ap_bar_h`)得出,本表不复制现算值 |
装板可行性由上面的不等式保证;格边长的现行算例与可读下限见 [battle.md](../../ux/battle.md)。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。 装板可行性由上面的不等式保证;格边长的可读下限见 [battle.md](../../ux/battle.md),算例由样张渲染脚本按布局表现算、不入文档。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
### 提示与弹层:轻提示随起随消、重弹层排队进出 ### 提示与弹层:轻提示随起随消、重弹层排队进出
@@ -244,7 +244,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
## 调参表 ## 调参表
本系统的参数全部是呈现节奏与可读性常量,不是玩法数值——玩法数值一律归[数据配置](数据配置.md)的表,与本表无关。修订本文常量走文档修订流程。**本表常量无数据表承载,为文档面唯一事实源;入驻 .tres 时本列改指路。** 本系统的参数全部是呈现节奏与可读性常量,不是玩法数值——玩法数值一律归[数据配置](数据配置.md)的表,与本表无关。修订本文常量走文档修订流程。**本表常量无数据表承载,为文档面唯一事实源;入驻 .tres 时本列改指路。** 布局几何(B 区高 / C 区宽 / AP 条高 / 明细带两态高)已入驻 `game/data/tables/battle_layout.tres`,不在本表重复。
| 参数 | 域 | 现行值(文档自持,无表承载) | 说明 | | 参数 | 域 | 现行值(文档自持,无表承载) | 说明 |
|---|---|---|---| |---|---|---|---|
@@ -254,7 +254,6 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| clear_celebration_ms | 每次清图 | 3000–5000 | 上限约束:三五秒即收 | | clear_celebration_ms | 每次清图 | 3000–5000 | 上限约束:三五秒即收 |
| font_min_board / font_min_detail / font_min_tooltip | 1280×800 基座 | 18 / 14 / 13 px | 可访问性基线 | | font_min_board / font_min_detail / font_min_tooltip | 1280×800 基座 | 18 / 14 / 13 px | 可访问性基线 |
| reduce_motion_scale | 每段动画 | 0.5 或跳终态 | 减动效开关系数 | | reduce_motion_scale | 每段动画 | 0.5 或跳终态 | 减动效开关系数 |
| detail_band_height | 结算明细带 | 24 / 84 px | 摘要 / 实时流两态高度 |
结构冻结(非调参项,改动走文档修订):三区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 draw calls < 200、进入战斗屏 500ms 内可交互。 结构冻结(非调参项,改动走文档修订):三区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 draw calls < 200、进入战斗屏 500ms 内可交互。
@@ -262,7 +261,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
以下判据逐条可测,全部通过才算闭环达标。 以下判据逐条可测,全部通过才算闭环达标。
- 三区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区尺寸与 battle.md 布局表一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。 - 三区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区几何与布局表 `battle_layout.tres` 一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。
- 任意板型装板后网格实高不超出棋盘区:N × 格边长 + (N−1) × 2px ≤ 棋盘区可用高;数字字号与整数倍缩放判据见 battle.md。 - 任意板型装板后网格实高不超出棋盘区:N × 格边长 + (N−1) × 2px ≤ 棋盘区可用高;数字字号与整数倍缩放判据见 battle.md。
- 已开 / 未开格态在灰度截图下可逐格分类;已引爆危险格"可通行可读"(已开底加巢穴纹、可作移动目标)。 - 已开 / 未开格态在灰度截图下可逐格分类;已引爆危险格"可通行可读"(已开底加巢穴纹、可作移动目标)。
- 类型微光:微光只在英雄周围 8 格内的未开格显示;出圈即灭、入圈即亮;已开格与封锁格恒无微光;微光呈现不随格是雷是安变化(不泄危险格与安全格)。(迭代 30 卡①实装后转正式验收) - 类型微光:微光只在英雄周围 8 格内的未开格显示;出圈即灭、入圈即亮;已开格与封锁格恒无微光;微光呈现不随格是雷是安变化(不泄危险格与安全格)。(迭代 30 卡①实装后转正式验收)
@@ -70,6 +70,7 @@
| 关卡表 | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) | | 关卡表 | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) |
| 冒险表 | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) | | 冒险表 | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) |
| 元进度表 | 元进度字段(已建表 2026-09-21——现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族,顺带扩列) | [元进度](../跨局循环/元进度.md) | | 元进度表 | 元进度字段(已建表 2026-09-21——现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族,顺带扩列) | [元进度](../跨局循环/元进度.md) |
| 战斗屏布局表(`battle_layout.tres`) | 分区几何与明细带两态高(单行;呈现层布局配置,非玩法数值——供呈现层与样张工具同源读取) | [战斗屏规格](../../ux/battle.md)(结构)/[HUD框架](HUD框架.md)(呈现常量) |
举证:本节为数据纪律约定,无计算。违规检测 = 静态扫描加人工复核,随各表回填定稿与里程碑检查执行。 举证:本节为数据纪律约定,无计算。违规检测 = 静态扫描加人工复核,随各表回填定稿与里程碑检查执行。
+5 -4
View File
@@ -1,7 +1,7 @@
# 生涯 # 生涯
> **一句话定位**:营地里与三柜台平级的**局外只读界面**——营地管「花」(花钥石买永久强化),生涯管「看」(看自己跨局攒下的一切:累计战绩、历史冒险、图鉴收集)。冒险进行中不给任何数据回看(2026-10-04 用户裁决 P-C7 的延续),生涯是历史数据唯一的家。 > **一句话定位**:营地里与三柜台平级的**局外只读界面**——营地管「花」(花钥石买永久强化),生涯管「看」(看自己跨局攒下的一切:累计战绩、历史冒险、图鉴收集)。冒险进行中不给任何数据回看(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——冒险中不给数据回看,历史战报与结算明细的查看去向 = 新「生涯」界面)。 > **来源**:[待办清单「『生涯』界面——冒险中数据回看的唯一承接处」条](../../../../待办清单.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 甲) | | 状态面板(迭代 17 卡③,并行批开发中) | **局内**只读查看这一局进行中的钥石与本局冒险进度 | 状态面板看当局当前值,生涯看跨局累计与历史;局内无生涯入口(P4 甲) |
| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 | | 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 | | 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 |
@@ -82,7 +82,7 @@
| 期 | 内容 | 前置 | 建议窗口(候选) | | 期 | 内容 | 前置 | 建议窗口(候选) |
|---|---|---|---| |---|---|---|---|
| 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 schema 迁移——**✅ 已实装(2026-10-07 迭代 18 卡④)**:汇总段六类九项+营地与系统组生涯钮+存档 v6 两终身计数域〔v5 升 v6 幂等迁移〕;闸读数与代拍板追认单原记于该批出口报告,该件已按「吸收即死」删除,读数以本行与该批提交信息为准 | 提纲评审通过。实装批前置义务 = 对齐迭代 16 UI 批后的现行基线:字号六档 / 语义色 / 按钮最小高 40 点等现行界面基线 / 色板单源 / 页面壳的模态栈纪律——与卡③需求卡片同款红线,本文只登记、不展开 | Alpha 后最近一个 UI 窗口 | | 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 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 阶段) | | 期三 | 图鉴承接(敌人优先,范围与顺序随 P3 甲);「见过」记账面 schema 义务随该批执行 | 期二或独立评估 | Beta 窗口候选(与教学引导同段评估——图鉴的「认得」轴与教学相关;待办清单教学引导节各条 2026-10-04 起已整体标 Beta 阶段) |
本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装;文案权先例 = 教学文案定稿权登记于待办清单),实装批承接。 本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装;文案权先例 = 教学文案定稿权登记于待办清单),实装批承接。
@@ -91,9 +91,10 @@
P0-P5 六点已于 2026-10-06 用户全裁(案甲 × 6,备选案与淘汰依据在需求卡片 §8),结论已写入上文对应节,不重复列。本批新发现的待裁点一项: P0-P5 六点已于 2026-10-06 用户全裁(案甲 × 6,备选案与淘汰依据在需求卡片 §8),结论已写入上文对应节,不重复列。本批新发现的待裁点一项:
### P6 · 营地底栏「最近冒险」条带去留(期二必答项) ### P6 · 营地底栏「最近冒险」条带去留(已裁甲案并落地——2026-11-01 迭代 47 卡④)
**现状与问题**:营地底栏「最近冒险」条带把摘要逐行全部拼进同一个标签(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源与生涯历史回看同属一份最近摘要。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。 **现状与问题**:营地底栏「最近冒险」条带把摘要逐行全部拼进同一个标签(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源与生涯历史回看同属一份最近摘要。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。
**裁决与执行**:用户裁甲案(2026-11-01,随迭代 47 卡④ 四柜台拆解执行)——条带已撤下、逐局历史回看唯一归生涯;期二生涯历史回看批的条带处置义务随之清空。
| 案 | 内容 | 淘汰/保留依据 | | 案 | 内容 | 淘汰/保留依据 |
|---|---|---| |---|---|---|
+14 -18
View File
@@ -2,7 +2,7 @@
> **Status**: Complete(现行) > **Status**: Complete(现行)
> **Author**: user + ux-designer > **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) > **Platform Target**: PC (Steam)——Keyboard/Mouse 主(全部操作纯鼠标可完成)、Gamepad Partial 非 MVP、Touch None(同 technical-preferences)
> **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算的挖掘期刷新编排 + 恒同盘不变、系统索引的刷新规则同步——见 OQ11) > **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算的挖掘期刷新编排 + 恒同盘不变、系统索引的刷新规则同步——见 OQ11)
> **Template**: UX Spec > **Template**: UX Spec
@@ -115,13 +115,13 @@
### Layout Zones ### Layout Zones
采用用户拍板的布局图(2026-09-12),按 1280×800 基座落尺寸: 采用用户拍板的布局图(2026-09-12),按 1280×800 基座落尺寸。**分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`**(经 `ConfigDb.presentation.battle_layout` 读取;字段 `stage_h` / `info_panel_w` / `ap_bar_h` / `log_band_h_dig` / `log_band_h_settle`)。本节只写结构关系与内容职责,不复写数值;任何文档与代码不另立第二份布局值。
| 区 | 位置与尺寸 | 内容 | | 区 | 位置与几何 | 内容 |
|---|---|---| |---|---|---|
| **B 战斗画面** | 全宽 × 270px(2026-10-06 方案二拍板:演出 ≈1/3 屏,原 252) | 主角站桩立绘(左)+ 兑现演出区(中)+ 敌人站桩立绘 ×1–3(右)——**全站桩形态(2026-09-20 用户拍板,敌人卡形态待办裁决完成):主角与敌人均为无框立绘+底座,零卡片**;主角与敌人站桩 = **HP 与状态效果的唯一展示面(2026-09-26 信息归属拍板)**;底部结算明细带(24px 摘要 / 结算期 84px 实时流);顶部中央轮次·阶段指示 | | **B 战斗画面** | 全宽,高读 `stage_h`(2026-10-06 方案二拍板:演出 ≈1/3 屏) | 主角站桩立绘(左)+ 兑现演出区(中)+ 敌人站桩立绘 ×1–3(右)——**全站桩形态(2026-09-20 用户拍板,敌人卡形态待办裁决完成):主角与敌人均为无框立绘+底座,零卡片**;主角与敌人站桩 = **HP 与状态效果的唯一展示面(2026-09-26 信息归属拍板)**;底部结算明细带(挖掘期收起高读 `log_band_h_dig`,结算期展开高读 `log_band_h_settle`);顶部中央轮次·阶段指示 |
| **C 角色信息** | 左下 240px × 530px(2026-10-06 方案二:548→530) | 攻击值(含修正显示)、护甲 AC;秘宝槽、奇物列表(现行标题「特性」)——HP 条/RAP 池/状态效果槽/立绘占位/钱包行退役(2026-09-26 信息归属拍板:HP 与状态唯一展示面 = B 区站桩;RAP 迁 D 区 AP 条;GP/钥石迁出战斗屏) | | **C 角色信息** | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 攻击值(含修正显示)、护甲 AC;秘宝槽、奇物列表(现行标题「特性」)——HP 条/RAP 池/状态效果槽/立绘占位/钱包行退役(2026-09-26 信息归属拍板:HP 与状态唯一展示面 = B 区站桩;RAP 迁 D 区 AP 条;GP/钥石迁出战斗屏) |
| **D 扫雷图** | 中下 ~1036px × 530px(2026-10-06 方案二:548→530——压缩全部由面板空档吸收,**棋盘 438×438 原尺寸不缩、格子不变**;原右下面板删除后横向加宽,吸收其宽度) | 顶部 AP 条(44px:左——AP 点阵 + 停手按钮;中——RAP 点阵+数字〔自 C 区迁入,贴弃图刷新按钮,2026-09-26 拍板〕;右——图例按钮 + 弃图刷新按钮〔雷图右上角,2026-09-12 拍板〕);棋盘主体(居中,格边长见下方「棋盘格尺寸公式」);toast 槽(上缘内侧) | | **D 扫雷图** | 中下,宽 = 基准屏宽 − C 区宽 − 列间距,高同 C 区(2026-10-06 方案二:底行压缩全部由面板空档吸收,**棋盘原尺寸不缩、格子不变**;原右下面板删除后横向加宽,吸收其宽度) | 顶部 AP 条(高读 `ap_bar_h`:左——AP 点阵 + 停手按钮;中——RAP 点阵+数字〔自 C 区迁入,贴弃图刷新按钮,2026-09-26 拍板〕;右——图例按钮 + 弃图刷新按钮〔雷图右上角,2026-09-12 拍板〕);棋盘主体(居中,格边长见下方「棋盘格尺寸公式」);toast 槽(上缘内侧) |
> 布局为 B/C/D 三区——关卡进度条不在战斗屏(节点链进度常驻关卡画面,见 Navigation Position;字母不重编沿用 B/C/D)。 > 布局为 B/C/D 三区——关卡进度条不在战斗屏(节点链进度常驻关卡画面,见 Navigation Position;字母不重编沿用 B/C/D)。
@@ -141,15 +141,11 @@
``` ```
- N = 棋盘长宽两边的较大者;格间距 2px;垂直预留 24px(上 12 / 下 12);悬崖厚度 8px;结构钳制区间 [26, 46]px。 - 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`。 - N 的取值与每关板型的对应关系读[棋盘生成](../systems/排雷循环/棋盘生成.md),现行值的事实源是 `data/tables/board_gen.tres`。
- 数字字号 = max(18, ceil(格边长 × 0.5)),像素字体整数倍缩放(canvas_items stretch 保证清晰)。 - 数字字号 = max(18, ceil(格边长 × 0.5)),像素字体整数倍缩放(canvas_items stretch 保证清晰)。
按 1280×800 基准档、棋盘区可用高 486px(D 区 530 减行动点条 44)推算的算例(随布局与板型变化,不是契约值): 算例不入文档:棋盘区可用面积随布局表与板型变化,不是契约值——现算并绘在整屏样张上,本文件不复写。
| 板型 N | 10 | 12 | 13 | 14 |
|---|---|---|---|---|
| 格边长 | 43px | 36px | 33px | 30px |
### Component Inventory ### Component Inventory
@@ -196,12 +192,13 @@
### ASCII Wireframe ### ASCII Wireframe
> **2026-09-26 信息归属拍板**:C 区 HP/RAP/状态槽/立绘占位/钱包行退役——HP 与状态唯一展示面 = B 区站桩,RAP 迁 D 区 AP 条贴弃图刷新按钮,GP/钥石迁出战斗屏;C 区改攻击数据框+两预留区。下方线框图已随拍板重绘;旧线框留档于 git 历史。 > **2026-09-26 信息归属拍板**:C 区 HP/RAP/状态槽/立绘占位/钱包行退役——HP 与状态唯一展示面 = B 区站桩,RAP 迁 D 区 AP 条贴弃图刷新按钮,GP/钥石迁出战斗屏;C 区改攻击数据框+两预留区。下方线框图已随拍板重绘;旧线框留档于 git 历史。
> **2026-09-23 停点前移批**:A 区关卡进度条退役,下方线框图为 B/C/D 三区(28px 纵向空间划归 C/D 底行 520 → 548;关卡内节点进度常驻关卡画面,战斗屏零进度承载);旧四区线框留档于 git 历史。 > **2026-09-23 停点前移批**:A 区关卡进度条退役,下方线框图为 B/C/D 三区(纵向空间划归 C/D 底行;关卡内节点进度常驻关卡画面,战斗屏零进度承载);旧四区线框留档于 git 历史。
> **几何标注已撤**:线框只示意结构,各区几何读 `game/data/tables/battle_layout.tres`,图中不标像素值。
> **2026-09-20 全站桩形态改版**:敌人卡形态裁决完成(卡片式退役、主角与敌人均站桩——无框立绘+底座、状态小图标挂血条下),下方线框图 B 区已随裁决重绘;旧卡片式线框留档于 git 历史。 > **2026-09-20 全站桩形态改版**:敌人卡形态裁决完成(卡片式退役、主角与敌人均站桩——无框立绘+底座、状态小图标挂血条下),下方线框图 B 区已随裁决重绘;旧卡片式线框留档于 git 历史。
``` ```
1280 × 800 ──────────────────────────────────────────────────────────────────── 1280 × 800 ────────────────────────────────────────────────────────────────────
┌─[B] 战斗画面 270px ──────────────────────────────────────────────────────────┐ ┌─[B] 战斗画面(高读布局表) ──────────────────────────────────────────────────┐
│ /小人\ 第 3 轮 · 挖掘中 伏×2 伏×2 │ │ /小人\ 第 3 轮 · 挖掘中 伏×2 伏×2 │
│ (落影) [ 兑现演出区 ] /蛆虫\ /蛆虫\ │ │ (落影) [ 兑现演出区 ] /蛆虫\ /蛆虫\ │
│ HP▓▓▓▓░ (结算期: 光带→弹道→受击) ①HP▓▓▓░ ②HP▓▓░░ │ │ HP▓▓▓▓░ (结算期: 光带→弹道→受击) ①HP▓▓▓░ ②HP▓▓░░ │
@@ -209,8 +206,7 @@
│ ├─ 结算明细带 ────────────────────────────────────────────────────────────┤ │ │ ├─ 结算明细带 ────────────────────────────────────────────────────────────┤ │
│ │ ▸上轮: 主角攻击3→蛆虫-3 | 攻击+50%(1结算) | 警觉+1×2 … [展开明细▾] │ │ # 2026-09-17 数值压缩批:UI 示意文本同步(atk 7→3〔地精格数÷3 + L1 锚行;UX 示意按当前地精行值读〕、伏击 +3 → +1/层,幅度参考 data/tables/) │ │ ▸上轮: 主角攻击3→蛆虫-3 | 攻击+50%(1结算) | 警觉+1×2 … [展开明细▾] │ │ # 2026-09-17 数值压缩批:UI 示意文本同步(atk 7→3〔地精格数÷3 + L1 锚行;UX 示意按当前地精行值读〕、伏击 +3 → +1/层,幅度参考 data/tables/)
│ └────────────────────────────────────────────────────────────────────────┘ │ │ └────────────────────────────────────────────────────────────────────────┘ │
├─[C] 角色信息 240px ───┬─[D] 扫雷图 ~1036px ──────────────────────────────────┤ ├─[C] 角色信息 ─────────┬─[D] 扫雷图 ───────────────────────────────────────────┤
│ 548px │ 548px │
│ ┌──────────────────┐ │ ┌─────────────────────────────────────────────────┐ │ │ ┌──────────────────┐ │ ┌─────────────────────────────────────────────────┐ │
│ │ 攻击 6 (+50%) │ │ │ AP ●●●●●○○○ 5/8 RAP ▓▓▓░ 3/8 │ │ │ │ 攻击 6 (+50%) │ │ │ AP ●●●●●○○○ 5/8 RAP ▓▓▓░ 3/8 │ │
│ │ 护甲 AC 12 │ │ │ [⏹停手] [?图例] [↻刷新 8|剩2] │ │ │ │ 护甲 AC 12 │ │ │ [⏹停手] [?图例] [↻刷新 8|剩2] │ │
@@ -232,7 +228,7 @@
| State / Variant | Trigger | What Changes | | State / Variant | Trigger | What Changes |
|---|---|---| |---|---|---|
| **挖掘阶段(默认态)** | 轮次开始 / 结算完毕回 Active 相位 | B 区敌人待机(意图徽标全程可见——敌人系统 Rule 8⑤),D 区棋盘可交互,AP 条点亮;能量 2/10,唯悬停呼吸微光在动(§2.1) | | **挖掘阶段(默认态)** | 轮次开始 / 结算完毕回 Active 相位 | B 区敌人待机(意图徽标全程可见——敌人系统 Rule 8⑤),D 区棋盘可交互,AP 条点亮;能量 2/10,唯悬停呼吸微光在动(§2.1) |
| **结算阶段** | round_end 触发(AP 尽/停手/清图) | D 区锁输入(棋盘去饱和 + 金橙色相偏移,明度不变,数字保持全对比);B 区播放五段编排演出(授予段 → 增益/减益段 → 战斗段 → 衰减段 → 判定段,战斗结算 段序冻结);明细带展开 84px 逐行滚动;阶段指示→"结算中";能量 7/10 | | **结算阶段** | round_end 触发(AP 尽/停手/清图) | D 区锁输入(棋盘去饱和 + 金橙色相偏移,明度不变,数字保持全对比);B 区播放五段编排演出(授予段 → 增益/减益段 → 战斗段 → 衰减段 → 判定段,战斗结算 段序冻结);明细带按结算期档展开(高读 `log_band_h_settle`)逐行滚动;阶段指示→"结算中";能量 7/10 |
| **清图瞬间** | 全部安全格开完 | 金粒喷泉 + 彩纸 3–5s 即收(§2.3);清图奖励行强调呈现(H2,地图战斗资源×④——金框高亮行);随后换新图 install | | **清图瞬间** | 全部安全格开完 | 金粒喷泉 + 彩纸 3–5s 即收(§2.3);清图奖励行强调呈现(H2,地图战斗资源×④——金框高亮行);随后换新图 install |
| **完美封印高光** | 战斗结算高光判定(全图标中/零误标类) | 全屏喝彩级反馈(§2.2 最高档):金光带加倍扫射 + 「完美封印 ×2」横幅——P2 爆发兑现节点(创意风险 #2 落点) | | **完美封印高光** | 战斗结算高光判定(全图标中/零误标类) | 全屏喝彩级反馈(§2.2 最高档):金光带加倍扫射 + 「完美封印 ×2」横幅——P2 爆发兑现节点(创意风险 #2 落点) |
| **封印标中高光** | 封印判定·标中 | 该印记化暴击弹道加重 + 明细行金框——chord 缺席的即时兑现补偿(创意风险 #3 落点) | | **封印标中高光** | 封印判定·标中 | 该印记化暴击弹道加重 + 明细行金框——chord 缺席的即时兑现补偿(创意风险 #3 落点) |
@@ -370,7 +366,7 @@
- [ ] 两种格态(已开格 / 未开格)灰度截图下一眼可辨(排雷玩法×6②) - [ ] 两种格态(已开格 / 未开格)灰度截图下一眼可辨(排雷玩法×6②)
- [ ] 悬停光标区分移动/挖/封印三语义;可挖域(8 向相邻)常驻指示;非相邻格悬停显禁止反馈(排雷玩法×6③) - [ ] 悬停光标区分移动/挖/封印三语义;可挖域(8 向相邻)常驻指示;非相邻格悬停显禁止反馈(排雷玩法×6③)
- [ ] 已引爆危险格呈"可通行可读"(排雷玩法×6④) - [ ] 已引爆危险格呈"可通行可读"(排雷玩法×6④)
- [ ] 全部数字对所在格底对比 ≥4.5:1;明度分层规则无违例(art-bible §2/§4.1,v3.0——状态区分不走明度,格底功能分档合法) - [ ] 全部数字对所在格底对比 ≥4.5:1;明度分层规则无违例(art-bible §2/§4.1——格底功能分档合法,格态语义由形与色相承载)
**交互与反馈** **交互与反馈**
+7 -5
View File
@@ -1,6 +1,6 @@
# 营地场景 UX spec(camp) # 营地场景 UX spec(camp)
> 状态:v2.1(现行) > 状态:v2.4(现行)
> 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制) > 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制)
> 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.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)(主菜单语境) > 相邻:[level-map.md](level-map.md)(主菜单语境)
@@ -24,9 +24,11 @@
纵深=远景天空与远山/中景木围墙与营地大门/近景篝火与左右物件(作者纵深稿)。中景围墙是横贯全宽的密排竖木桩墙——木桩一根挨一根无缝、桩顶削尖、无横木,只在中段留缺口给大门。 纵深=远景天空与远山/中景木围墙与营地大门/近景篝火与左右物件(作者纵深稿)。中景围墙是横贯全宽的密排竖木桩墙——木桩一根挨一根无缝、桩顶削尖、无横木,只在中段留缺口给大门。
**中景带自画面底部往上约 2/3 处起**;近景物件允许压住围墙(围墙是纯背景板),**唯一例外=中间门洞必须留空、不被任何物件遮挡**。围墙形态、门扇比例与帐篷尺寸的现行口径归 [art-bible](../../art/art-bible.md) §6.2,本 spec 不再复述。 **场景按要素分层(10 张)**:远景天空/远山/整片地面/门洞内的小路合成 `scene_camp_bg` 背景层(z0,不透明全幅);中景木围墙独立成 `scene_camp_wall` 围墙层(z1,透明底,门洞处留透明缺口);其余八个物件层各一张、由上往下叠。门洞是围墙层的透明缺口,透出背景层画的**一条通向远处的小路**,不可填死。
坐标草案与验收判据见 [提案](../../../reports/提案-营地场景图美术生成方案-2026-10-22.md) §2.5/§5。 **中景带自画面底部往上约 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 美术方案讨论沿革查 git 提交史。
## 点选反馈(根态) ## 点选反馈(根态)
@@ -36,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
@@ -81,7 +81,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Category**: Data Display **Category**: Data Display
**Used In**: 战斗画面区底部(结算期第一注视点) **Used In**: 战斗画面区底部(结算期第一注视点)
**Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开 84px 实时流;点击开完整回看 overlay。 **Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开为实时流(带高读 `log_band_h_settle`);点击开完整回看 overlay。
**Specification**: **Specification**:
- 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败** - 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败**
+1 -1
View File
@@ -234,6 +234,6 @@
| [HUD框架](../systems/表现与支撑/HUD框架.md) | 模态 overlay 层级纪律、订阅纪律(推进信号两线之一) | Navigation Position + Data Requirements 架构注记 | | [HUD框架](../systems/表现与支撑/HUD框架.md) | 模态 overlay 层级纪律、订阅纪律(推进信号两线之一) | Navigation Position + Data Requirements 架构注记 |
| HUD框架 | 可访问性基线全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体) | Accessibility | | HUD框架 | 可访问性基线全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体) | Accessibility |
| [battle.md](battle.md) OQ14 二轮拍板 | GP = 关卡画面常驻总量 | 顶栏 GP 常驻徽章 | | [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") _reject("phase")
return false return false
var cost := _cost("dig") var cost := _cost("dig")
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前: # dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前——施加单点 _apply_dig_paid_hits):
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。 # counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。
# counter_add〔双管镐 付费挖格计数〕/ grant_ap〔每第 2 次返 1 AP——对价减免, var apply_res := _apply_dig_paid_hits(cost)
# mod_eq 闸在行 when〕同面施加) 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 dig_refund := 0
var rap_gain := 0
if _fx != null: if _fx != null:
for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}): for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
if String(h.get("kind", "")) == "counter_spend": 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) 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": elif String(h.get("kind", "")) == "grant_ap":
dig_refund += int(h.get("value", 0)) dig_refund += int(h.get("value", 0))
if _ap < cost: elif String(h.get("kind", "")) == "grant_reserve_ap":
_reject("resource_face") rap_gain += int(h.get("value", 0))
return false return {"cost": cost, "refund": dig_refund, "rap_gain": rap_gain}
if dig_refund > 0:
cost = maxi(0, cost - dig_refund) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
return _spend_cost_and_arbitrate(cost, clears_map)
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool: 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 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算 # Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。 # (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: if _phase != Phase.ACTIVE:
_reject("phase") _reject("phase")
return false 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 资源不足拒绝——合法相位内资源拒绝,非编排错误 return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
_rap -= db.run.action_economy.rap_refresh_cost _rap -= cost
return true return true
+61 -231
View File
@@ -27,19 +27,14 @@ var levels_total := 0 # 序列总关数(读 level 表行
var master_seed := 0 # 本局主种子(摘要第五件事) var master_seed := 0 # 本局主种子(摘要第五件事)
var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成) var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成)
var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离) var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离)
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
var _treasure_unlocked := {} # aux 解锁门控集(params.treasure_unlocked 接收面) var _treasure_unlocked := {} # aux 解锁门控集(params.treasure_unlocked 接收面)
var _attunement: Array[Variant] = [] # 同调装配集(params.attunement 接收面,start_level 外下传 LevelRunner) var _attunement: Array[Variant] = [] # 同调装配集(params.attunement 接收面,start_level 外下传 LevelRunner)
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
# ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)---- # ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)----
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主) var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件) var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收) var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收)
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源) var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
var _bp_store: BreakpointStore # 断点组装与回放件(_init 建;六成员+27 键登记单源+读写侧机组随件——宿主读写经件)
var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面) var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面)
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面) var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool) var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
@@ -57,6 +52,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
_snapshot_breakpoint) _snapshot_breakpoint)
_loadout = AttuneLoadout.new(db, _attunement) _loadout = AttuneLoadout.new(db, _attunement)
adventure_inventory = TreasureItems.new(db) adventure_inventory = TreasureItems.new(db)
_bp_store = BreakpointStore.new({"db": db, "rng": rng, "stats": _stats, "wallet": _wallet,
"journal": _journal, "curio": _curio, "stop": _stop, "bank": _refresh_bank, "loadout": _loadout})
# 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块) # 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块)
@@ -107,7 +104,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_reset_curio_state() _reset_curio_state()
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数) levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = 1 level_index = 1
_pending_next_level = false _bp_store._pending_next_level = false
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动) _shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {} _treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive _attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
@@ -115,7 +112,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池) _refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面) _loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
adventure_inventory.reset() # 冒险级库存重置(新局空库存) adventure_inventory.reset() # 冒险级库存重置(新局空库存)
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后) _bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零) adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓: # 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕) # 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
@@ -152,99 +149,56 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
return false return false
master_seed = int(bp.get("master_seed", 0)) master_seed = int(bp.get("master_seed", 0))
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0))) rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
_resume_ledger(bp, params) # ---- 断点回放(R2 整族归件——件侧语句入 _bp_store、宿主侧语句按原语句序留本壳,序逐步保形)----
_resume_curio_state(bp) _bp_store.restore_ledger(bp, params) # 续玩账面回填——主角账三件片段各还原各键(件侧;clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
_resume_params(bp, params) summary = {}
_resume_counters(bp) levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = int(bp.get("level_index", 1))
_bp_store._pending_next_level = false
_reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典)
_bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
_bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧)
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
_bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面)
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
# 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
var bp_counters = _bp_store._bp_read("adventure_counters", bp)
if bp_counters is Dictionary:
adventure_counters = (bp_counters as Dictionary).duplicate(true)
return _resume_pending_or_start(bp)
# 续玩收束段(宿主侧——停点直回/已兑付重发/无停点续玩起步;R2 回放段的尾半,语句序与
# resume_adventure 内联时逐句相同):三选一停点态与待推进位经件读写,选路停点快照单跳传递。
func _resume_pending_or_start(bp: Dictionary) -> bool:
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
if not bp_pending.is_empty(): if not bp_pending.is_empty():
_pending_next_level = true _bp_store._pending_next_level = true
_state = State.RUNNING _state = State.RUNNING
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继) _stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
return true return true
if not bp_result.is_empty(): if not bp_result.is_empty():
_pending_next_level = true _bp_store._pending_next_level = true
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件) _stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
_state = State.RUNNING _state = State.RUNNING
return true return true
_resume_bp = bp.duplicate(true) _bp_store._resume_bp = bp.duplicate(true)
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory) # 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
_loadout_seeded = false _bp_store._loadout_seeded = false
_loadout.set_attunement(_attunement) _loadout.set_attunement(_attunement)
adventure_inventory.reset() adventure_inventory.reset()
_state = State.RUNNING _state = State.RUNNING
return _start_current_level() return _start_current_level()
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
func _resume_ledger(bp: Dictionary, params: Dictionary) -> void:
_stats.restore_fragment(bp, params)
_wallet.restore_fragment(bp, params)
_journal.restore_fragment(bp, params)
summary = {}
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = int(bp.get("level_index", 1))
_pending_next_level = false
# 续玩三选一态回填(池与件计数归件、停点态归本类;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
func _resume_curio_state(bp: Dictionary) -> void:
_reset_curio_state()
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
for id in _curio.taken.keys():
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in bp_taken.keys():
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# relic 令牌 feat_id 随还原)
var bp_deck = _bp_read("bonus_deck", bp)
if bp_deck is Array:
var restored: Array[Variant] = []
for t in (bp_deck as Array):
var tok: Dictionary[Variant, Variant] = t as Dictionary
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
if String(rtok["kind"]) == "relic":
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
restored.append(rtok)
_curio.deck = restored
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
_stats.feats = []
for r in (bp.get("relics", []) as Array):
_stats.feats.append(String(r))
# 续玩参数面回填(同 start_adventure 参数面;生命账本越界钳制在此落——clamp 类)
func _resume_params(bp: Dictionary, params: Dictionary) -> void:
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
_stats.armor_type = String(params.get("armor_type", ""))
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数)
_refresh_bank.restore_fragment(bp, params) # 刷新账片段回填(刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
# 续玩计数器回填(冒险域计数——凭证计数/刷新存量/Y 池随停点件片段回填)
func _resume_counters(bp: Dictionary) -> void:
# adventure 域计数器回填(缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
var bp_counters = _bp_read("adventure_counters", bp)
if bp_counters is Dictionary:
adventure_counters = (bp_counters as Dictionary).duplicate(true)
func _reset_curio_state() -> void: func _reset_curio_state() -> void:
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理) _curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
_stop.reset_adventure_state() _stop.reset_adventure_state()
_loadout_seeded = false _bp_store._loadout_seeded = false
_stats.feats = [] _stats.feats = []
adventure_counters = {} adventure_counters = {}
@@ -263,7 +217,7 @@ func effective_max_hp() -> int:
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默) # 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
func set_save_manager(sm: SaveManagerCore) -> void: func set_save_manager(sm: SaveManagerCore) -> void:
_save_manager = sm _bp_store._save_manager = sm
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取) # 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
@@ -278,13 +232,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int:
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进) # 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
func continue_to_next_level() -> bool: func continue_to_next_level() -> bool:
if not _pending_next_level: if not _bp_store._pending_next_level:
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进") push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
return false return false
if _stop.curio_stop_pending(): if _stop.curio_stop_pending():
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)") push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
return false return false
_pending_next_level = false _bp_store._pending_next_level = false
_stop.clear_resolved() _stop.clear_resolved()
level_index += 1 level_index += 1
return _start_current_level() return _start_current_level()
@@ -343,9 +297,9 @@ func _loadout_on_level_inventory() -> void:
var inv = _runner.treasure_inventory var inv = _runner.treasure_inventory
if inv == null: if inv == null:
return return
if not _loadout_seeded: if not _bp_store._loadout_seeded:
_loadout_seeded = true _bp_store._loadout_seeded = true
var bp_charges: Variant = _bp_read("attunement_charges", _resume_bp, null) if not _resume_bp.is_empty() else null var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null
if bp_charges is Dictionary: if bp_charges is Dictionary:
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数 _loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
else: else:
@@ -368,8 +322,8 @@ func _loadout_on_level_inventory() -> void:
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因) # apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
func _boss_keystone_drop(li: int) -> void: func _boss_keystone_drop(li: int) -> void:
var first := true var first := true
if _save_manager != null: if _bp_store._save_manager != null:
first = not _save_manager.meta_state.career_state().boss_first_kill.has(str(li)) first = not _bp_store._save_manager.meta_state.career_state().boss_first_kill.has(str(li))
var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li) var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li)
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0)) var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语 _journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
@@ -419,9 +373,9 @@ func _start_current_level() -> bool:
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解), # 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。 # resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
var resume_state: Dictionary[Variant, Variant] = {} var resume_state: Dictionary[Variant, Variant] = {}
if not _resume_bp.is_empty(): if not _bp_store._resume_bp.is_empty():
resume_state = {"node_id": int(_bp_read("node_id", _resume_bp, -1)), resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)),
"visited_ids": _bp_read("visited_ids", _resume_bp, []) as Array} "visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array}
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好); # 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。 # 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在 # defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
@@ -431,7 +385,7 @@ func _start_current_level() -> bool:
if ok: if ok:
_emit_level_started() _emit_level_started()
_present_level_entry(resume_state) _present_level_entry(resume_state)
_resume_bp = {} _bp_store._resume_bp = {}
return ok return ok
@@ -455,7 +409,7 @@ func _present_level_entry(resume_state: Dictionary) -> void:
if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0: if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0:
_runner.present_entry_choice() _runner.present_entry_choice()
else: else:
_runner.restore_treasure_items(_bp_read("aux_items", _resume_bp, []) as Array) _runner.restore_treasure_items(_bp_store._bp_read("aux_items", _bp_store._resume_bp, []) as Array)
_runner.present_resumed_choice() _runner.present_resumed_choice()
@@ -464,7 +418,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if state != "running": if state != "running":
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state) push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
return return
_last_level_payload = payload.duplicate(true) _bp_store._last_level_payload = payload.duplicate(true)
var result := String(payload.get("result", "")) var result := String(payload.get("result", ""))
if result == "defeat": if result == "defeat":
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径) _stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
@@ -473,7 +427,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if result != "victory": if result != "victory":
push_error("[冒险结构] 关卡结束结果出域: %s" % result) push_error("[冒险结构] 关卡结束结果出域: %s" % result)
return return
_last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面) _bp_store._last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone) # Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
_boss_keystone_drop(level_index) _boss_keystone_drop(level_index)
if level_index < levels_total: if level_index < levels_total:
@@ -481,7 +435,7 @@ func _on_level_finished(payload: Dictionary) -> void:
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位); #(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算, # 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp) # 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
_pending_next_level = true _bp_store._pending_next_level = true
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true) var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
stop_base["gp"] = _wallet.gp stop_base["gp"] = _wallet.gp
_stop.open_level_clear(stop_base) _stop.open_level_clear(stop_base)
@@ -499,7 +453,7 @@ func _settle(result: String) -> void:
"stopped_at": 0 if result == "completed" else level_index, "stopped_at": 0 if result == "completed" else level_index,
"keystone_total": _journal.keystone_gained(), "keystone_total": _journal.keystone_gained(),
"master_seed": master_seed, "master_seed": master_seed,
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷) "aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源) "keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档) "vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面) "pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
@@ -507,19 +461,19 @@ func _settle(result: String) -> void:
} }
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源; # 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知) # 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
if result == "failed" and not _last_level_payload.is_empty(): if result == "failed" and not _bp_store._last_level_payload.is_empty():
summary["defeat"] = { summary["defeat"] = {
"level_index": level_index, "level_index": level_index,
"node_id": int(_last_level_payload.get("node_id", -1)), "node_id": int(_bp_store._last_level_payload.get("node_id", -1)),
"node_type": String(_last_level_payload.get("node_type", "")), "node_type": String(_bp_store._last_level_payload.get("node_type", "")),
} }
if _runner != null: if _runner != null:
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点) summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现) _runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化) _reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
if _save_manager != null: if _bp_store._save_manager != null:
_save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点) _bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
_save_manager.write_to_disk() _bp_store._save_manager.write_to_disk()
_state = State.SETTLING _state = State.SETTLING
adventure_finished.emit(summary) adventure_finished.emit(summary)
_state = State.IDLE _state = State.IDLE
@@ -530,131 +484,7 @@ func _on_level_choices_presented(_candidates: Array) -> void:
_snapshot_breakpoint() _snapshot_breakpoint()
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)---- # ---- 断点快照委托(组装与读侧策略机组归 breakpoint_store 件——六成员+27 键登记单源随件;
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表: # 本壳一行转发:CurioStop 构造注入的 Callable 目标与本类 _on_level_choices_presented 调用点零改)----
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
const BREAKPOINT_FIELDS := {
"level_index": {"strategy": "plain"},
"node_id": {"strategy": "plain"},
"hero_hp": {"strategy": "clamp"},
"bonus": {"strategy": "clamp"},
"gp": {"strategy": "plain"},
"shop_purchased": {"strategy": "plain"},
"shop_shelf": {"strategy": "plain"},
"shop_visit_buys": {"strategy": "plain"},
"attunement_charges": {"strategy": "keep_on_null"},
"graph_gen_v2": {"strategy": "plain"},
"master_seed": {"strategy": "plain"},
"rng_counter": {"strategy": "plain"},
"keystone": {"strategy": "plain"},
"visited_ids": {"strategy": "plain"},
"aux_items": {"strategy": "plain"},
"bonus_taken": {"strategy": "dual_merge"},
"bonus_deck": {"strategy": "keep_on_null"},
"relics": {"strategy": "plain"},
"bonus_pending": {"strategy": "token_migration"},
"feat_ticket_pity": {"strategy": "plain"},
"feat_ticket_sold": {"strategy": "plain"},
"adventure_counters": {"strategy": "keep_on_null"},
"vision_counter_used": {"strategy": "clamp"},
"refresh_stock": {"strategy": "clamp"},
"attune_refresh_y": {"strategy": "params_fallback"},
"bonus_result": {"strategy": "plain"},
"keystone_drops": {"strategy": "plain"},
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
}
func _snapshot_breakpoint() -> void: func _snapshot_breakpoint() -> void:
if _save_manager == null or state != "running": _bp_store.snapshot(state, level_index, master_seed, _runner, adventure_counters)
return
var parts: Dictionary[Variant, Variant] = _bp_parts()
var bp: Dictionary[Variant, Variant] = {}
for key in BREAKPOINT_FIELDS.keys():
bp[key] = parts[key]
_save_manager.set_breakpoint(bp)
_save_manager.write_to_disk()
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
func _bp_parts() -> Dictionary[Variant, Variant]:
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
var level_frag: Dictionary[Variant, Variant] = _runner.breakpoint_fragment() if _runner != null else {
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 _runner 为 null——节点域按缺省落档
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
var parts: Dictionary[Variant, Variant] = {
"level_index": level_index,
"node_id": int(level_frag.get("node_id", -1)),
"hero_hp": int(stats_frag.get("hero_hp", 0)),
"bonus": journal_frag.get("bonus", {}),
"gp": int(wallet_frag.get("gp", 0)),
"shop_purchased": journal_frag.get("shop_purchased", {}),
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
"attunement_charges": (_loadout.breakpoint_fragment(_runner.treasure_inventory) if _runner != null and _runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
"master_seed": master_seed,
"rng_counter": rng.counter(),
"keystone": int(journal_frag.get("keystone", 0)),
"visited_ids": level_frag.get("visited_ids", []),
"aux_items": level_frag.get("aux_items", []),
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
"adventure_counters": adventure_counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
}
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
var stray: Array[Variant] = []
for key in parts.keys():
if not BREAKPOINT_FIELDS.has(key):
stray.append(key)
if not stray.is_empty():
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
return parts
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
if not BREAKPOINT_FIELDS.has(key):
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
var v: Variant = bp.get(key, fallback)
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
return null # 类型不符——保持既有态(调用方跳过覆盖)
return v
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
func _bp_strategy(key: String) -> String:
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
match _bp_strategy(key):
"clamp":
return maxi(0, int(bp.get(key, fallback)))
"params_fallback":
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
_:
return int(bp.get(key, fallback))
+48
View File
@@ -0,0 +1,48 @@
## 战斗事实件:战斗事实的唯一记录面——本战受理事实(encounter:打什么、带什么进来)、
## 同调装配事实(attunement)、轮 facts 缓冲(reveals/seals/path)、封印判定账(去重两集+完美三账)、
## 装板溯源(seq/provenance)、条件事实计数(refresh_used、feat_first_dig_used、attune_safe_seen)。
## 零注入引用、零信号、零协作对象调用——纯数据+谓词+写口;缓冲写入点在宿主 A2 处理器,
## 身份快照捕获留宿主(fact 件不持协作引用,防它变成第二编排面)。
## 完美账写口单点=三 mark 方法(契约 2:perfect_hit_false 保持纯净,豁免只累加 perfect_forgiven);
## 其余成员公有直读写(段类/快照/宿主经 oc._facts.X——fx_host._fx_bonus_delta 跨件直读写同款形态)。
## reset_round 只清 reveals/seals/path 三项、不碰 refresh_used(_battle_over 后快照读到的
## refresh_used 必须保持末轮值)。
## 成员冻结:15/15 贴顶——实现期禁增员;扩容阀=先把封印判定账五成员二次拆出(SealLedger)再落新账。
class_name BattleFacts
extends RefCounted
var encounter: Dictionary[String, Variant] = {} # 本战受理事实(begin_battle assign 后全生命期只读)
var reveals: Array[Dictionary] = [] # 轮 facts 缓冲(cell_revealed 批)
var seals: Array[Dictionary] = [] # 封印判定批缓冲(seal_judged 批)
var path := "" # 轮末路径事实(round_end 载荷 path_tag)
var refresh_used := 0 # 本轮刷新计数(复位点=出口分发,语句随迁)
var board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
var board_from_midround := false # 装板 provenance:轮内刷新换来的图=刷新图(清图奖励不发)
var seal_consumed := {} # 封印兑付资格账(危险格实例为键;install 重置)
var seal_miss_seen := {} # 封印未中行去重(每安全格实例一次)
var perfect_hit_true := 0 # 整图完美封印计数(install 重置)
var perfect_hit_false := 0 # 完美误标计数(纯净禁回退改写——契约 2)
var perfect_forgiven := 0 # 完美保险豁免累计(单向累加自有账)
var feat_first_dig_used := false # 条件事实计数(魔矢每战首次)
var attunement: Array[Variant] = [] # 本战装配集快照(enc.attunement 受理携带;受理后只读)
var attune_safe_seen := 0 # 积点制件安全开格计数(装配集自动兑付进度)
# 轮复位(原宿主 _reset_round_facts 逐字——三调用点:begin_battle 首板 install 后/出口分发/终局序列)
func reset_round() -> void:
reveals.clear()
seals.clear()
path = ""
# 完美封印三账写口(授予段直写升级为账本单点——契约 2 执行点)
func mark_perfect_hit() -> void:
perfect_hit_true += 1
func mark_perfect_miss() -> void:
perfect_hit_false += 1
func mark_perfect_forgiven() -> void:
perfect_forgiven += 1
+1
View File
@@ -0,0 +1 @@
uid://bo0eolh0vmvgb
+95 -208
View File
@@ -3,7 +3,7 @@
## 效果引擎施加面归 BattleFxHost、奖励授予管线归 RewardGrant(两拆件,经组件出口 fx_host()/ ## 效果引擎施加面归 BattleFxHost、奖励授予管线归 RewardGrant(两拆件,经组件出口 fx_host()/
## reward_grant() 取件,禁链式穿透其内部);本类保留宿主职责面=状态机与生命周期、轮 facts 缓冲、 ## reward_grant() 取件,禁链式穿透其内部);本类保留宿主职责面=状态机与生命周期、轮 facts 缓冲、
## A2 三事件订阅、五段驱动、attune 族、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影、刷新面。 ## A2 三事件订阅、五段驱动、attune 族、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影、刷新面。
## 零 await;随机只经 rng 封闭流(encounter/behavior_roll/armor_roll/gp_drop)——同种子同输入逐位复现。 ## 零 await;随机只经 rng 封闭流(_facts.encounter/behavior_roll/armor_roll/gp_drop)——同种子同输入逐位复现。
## 呈现层读取唯一出口 = presentation_snapshot()/diagnostic_snapshot(),禁链式穿透内部协作对象。 ## 呈现层读取唯一出口 = presentation_snapshot()/diagnostic_snapshot(),禁链式穿透内部协作对象。
## 伤害只向 DamageController 开单(何时开单归编排、怎么算归控制器);刷新唯一通道 player_request_refresh,禁 RAP 满自动刷。 ## 伤害只向 DamageController 开单(何时开单归编排、怎么算归控制器);刷新唯一通道 player_request_refresh,禁 RAP 满自动刷。
class_name BattleOrchestrator class_name BattleOrchestrator
@@ -31,9 +31,12 @@ signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"] const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格; # 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
# fx_reveal=专长揭示掷格(落空行无真实格); # fx_reveal=专长揭示掷格(落空行无真实格);
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指) # fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指);
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_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——五段持有/段序/重入闸/摇号)
var db: ConfigDb var db: ConfigDb
var rng: RngServiceCore var rng: RngServiceCore
# E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。 # E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
@@ -45,60 +48,43 @@ var action_economy: ActionEconomy
var _enemy_system: UnitContainer var _enemy_system: UnitContainer
var _status_effects: StatusContainer var _status_effects: StatusContainer
var _reward_economy: RewardEconomy var _reward_economy: RewardEconomy
var treasure_inventory: TreasureItems = null:
set(v):
treasure_inventory = v
if _reward_grant != null:
_reward_grant.treasure_inventory = v # 库存注入转发(注入时序在宿主构造之后——外部赋值路径零改址)
var _damage: DamageController # 伤害控制器(单据唯一兑付管线) var _damage: DamageController # 伤害控制器(单据唯一兑付管线)
var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一) var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一)
var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二) var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二)
var encounter: Dictionary[String, Variant] = {}
var round_index := 0 var round_index := 0
var battle_result := "" # "" / "victory" / "defeat" / "fatal_error" var battle_result := "" # "" / "victory" / "defeat" / "fatal_error"
var _facts_reveals: Array[Dictionary] = []
var _facts_seals: Array[Dictionary] = []
var _facts_path := ""
var _settling := false
# 结算五段 Segment(段边界=审计五段,段序骨架冻结于 # 结算五段 Segment(段边界=审计五段,段序骨架冻结于
# SettlementTable.segment_order 语义:授予→增益/减益→战斗→衰减→判定。本类做段序编排+ # SettlementTable.segment_order 语义:授予→增益/减益→战斗→衰减→判定。本类做段序编排+
# 行发射/开单原语单点——段体逻辑归 game/core/settlement/ 五类) # 行发射/开单原语单点——段体逻辑归 game/core/settlement/ 五类)
var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点) # 同调装配运行态(begin_battle 经 enc 携带 attunement 键注入 facts;off=空集全部效果零扰动)
var _aura_seg = null # AuraSegment(②增益/减益段) # 转存时点=roll_round_actions 轮末(轮内部分结算不更新 last);开战清零归 begin_battle
var _combat_seg = null # CombatSegment(③战斗段)
var _decay_seg = null # DecaySegment(④衰减段)
var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归本类)
var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_cap_per_round)
var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
var _seal_consumed := {} # 兑付资格以危险格实例为键(整图;换图重置)
# 同调装配运行态(begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
var attunement: Array[Variant] = [] # 装备中同调件 item_id 集
var _mines_hits: Dictionary[Variant, Variant] = {"total": 0, "last": 0, "round": 0} # 踩雷计数(触发条件事实面——本场累计/上轮转存/当轮累计三读数合一账)
# 转存时点=_roll_round_actions 轮末(轮内部分结算不更新 last);开战清零归 begin_battle
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(装配集 ap_charge_safe 行扫描;满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
var nest_preview_count := -1 # 巢穴预告读数口(board_installed 命中落值;-1=未装配/未发射;呈现桥消费) var nest_preview_count := -1 # 巢穴预告读数口(board_installed 命中落值;-1=未装配/未发射;呈现桥消费)
# 特性运行态(relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经 # 特性运行态(relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动) # 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
var _feat_first_dig_used := false
# 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter.assign 后全生命期可读; # 主角攻击基值派生读(enc 携带 hero_atk 键受理后只读——E4 收口语义不变:表值运行期不可变,
# 派生值恒等;「读面即引用、无第二账」同款先例)
func _hero_atk() -> int:
return int(_facts.encounter.get("hero_atk", int(db.combat.hero.atk)))
# 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter 受理 assign 后全生命期可读;
# null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账) # null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账)
func _journal() -> GainJournal: func _journal() -> GainJournal:
return encounter.get("journal", null) if encounter.get("journal", null) is GainJournal else null return _facts.encounter.get("journal", null) if _facts.encounter.get("journal", null) is GainJournal else null
# 同调配装件读面(begin_battle 经 enc["loadout"] 派生——充能单源化的 reset_cycle/consume_charge # 同调配装件读面(begin_battle 经 enc["loadout"] 派生——充能单源化的 reset_cycle/consume_charge
# 消费面;null=直构零装配,充能面跳过) # 消费面;null=直构零装配,充能面跳过)
func _attune_loadout() -> AttuneLoadout: func _attune_loadout() -> AttuneLoadout:
return encounter.get("loadout", null) return _facts.encounter.get("loadout", null)
# 授予件战斗域回调束(bind_battle 注入——键集封闭三键:充能消耗/真视计数/负向踩雷计数; # 授予件战斗域回调束(bind_battle 注入——键集封闭三键:充能消耗/真视计数/负向踩雷计数;
# 宿主侧各转 _attune_loadout/_journal/_mines_hits 单点,授予件对账本零强引用) # 宿主侧各转 _attune_loadout/_journal/_rounds.mines_hits 单点,授予件对账本零强引用)
func _battle_hooks() -> Dictionary[Variant, Variant]: func _battle_hooks() -> Dictionary[Variant, Variant]:
return { return {
"attune_consume": _hook_attune_consume, "attune_consume": _hook_attune_consume,
@@ -108,8 +94,8 @@ func _battle_hooks() -> Dictionary[Variant, Variant]:
func _hook_attune_consume(item_id: String) -> void: func _hook_attune_consume(item_id: String) -> void:
if _attune_loadout() != null and treasure_inventory != null: if _attune_loadout() != null and _reward_grant.treasure_inventory != null:
_attune_loadout().consume_charge(item_id, treasure_inventory) _attune_loadout().consume_charge(item_id, _reward_grant.treasure_inventory)
func _hook_vision_add() -> void: func _hook_vision_add() -> void:
@@ -120,8 +106,8 @@ func _hook_vision_add() -> void:
# 负向 blast(雷)踩雷计数入账(触发条件事实面——正向 spark_aoe/清图奖励不计入; # 负向 blast(雷)踩雷计数入账(触发条件事实面——正向 spark_aoe/清图奖励不计入;
# 极性判定在授予件 _flat_blast 传点,本口只记账) # 极性判定在授予件 _flat_blast 传点,本口只记账)
func _book_negative_blast() -> void: func _book_negative_blast() -> void:
_mines_hits["round"] += 1 _rounds.mines_hits["round"] += 1
_mines_hits["total"] += 1 _rounds.mines_hits["total"] += 1
# 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实;charge_layers 归 fx_host 自答;未知事实回落 null) # 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实;charge_layers 归 fx_host 自答;未知事实回落 null)
@@ -136,10 +122,10 @@ func _fx_host_fact(fact: String) -> Variant:
"round_index": "round_index":
return round_index return round_index
"battle_index": "battle_index":
return int(encounter.get("battle_index", 1)) # 关卡域战斗序(1 基——level_runner enc 携带;开局姿态窗条件面) return int(_facts.encounter.get("battle_index", 1)) # 关卡域战斗序(1 基——level_runner enc 携带;开局姿态窗条件面)
"seals_count": "seals_count":
var hits_n := 0 var hits_n := 0
for fevt in _facts_seals: for fevt in _facts.seals:
if bool(fevt.get("hit", false)): if bool(fevt.get("hit", false)):
hits_n += 1 hits_n += 1
return hits_n return hits_n
@@ -151,7 +137,7 @@ func _fx_host_fact(fact: String) -> Variant:
# attune 积点读投影(RewardGrant attune_safe_seen_cb 注入面——use_treasure 拒绝文案读数;经读投影出户防组件回调宿主私有族) # attune 积点读投影(RewardGrant attune_safe_seen_cb 注入面——use_treasure 拒绝文案读数;经读投影出户防组件回调宿主私有族)
func _attune_safe_seen_read() -> int: func _attune_safe_seen_read() -> int:
return _attune_safe_seen return _facts.attune_safe_seen
# 组件出口(拆件后取件面——呈现与测试经此取件,禁链式穿透协作对象内部) # 组件出口(拆件后取件面——呈现与测试经此取件,禁链式穿透协作对象内部)
@@ -164,7 +150,7 @@ func reward_grant() -> RewardGrant:
func _attune_has(item_id: String) -> bool: func _attune_has(item_id: String) -> bool:
return attunement.has(item_id) return _facts.attunement.has(item_id)
# 装配集只读出口(_attune_has 契约化出口,save v6 只读访问器先例。只读零副作用—— # 装配集只读出口(_attune_has 契约化出口,save v6 只读访问器先例。只读零副作用——
@@ -179,7 +165,7 @@ func attune_has(item_id: String) -> bool:
# kind 分发查询先例=_attune_refresh_x_total bonus_refresh。零装配=0) # kind 分发查询先例=_attune_refresh_x_total bonus_refresh。零装配=0)
func attune_param_sum(kind: String) -> int: func attune_param_sum(kind: String) -> int:
var total := 0 var total := 0
for iid_v in attunement: for iid_v in _facts.attunement:
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v)) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v))
if (arow.get("effect_kinds", []) as Array).has(kind): if (arow.get("effect_kinds", []) as Array).has(kind):
total += int(arow.get("magnitude", 0)) total += int(arow.get("magnitude", 0))
@@ -189,7 +175,7 @@ func attune_param_sum(kind: String) -> int:
# 装配集内 ap_charge_safe 行读取(取数源=装配集行扫描; # 装配集内 ap_charge_safe 行读取(取数源=装配集行扫描;
# 首行语义=装配集内至多一件积点制〔试炼镐〕) # 首行语义=装配集内至多一件积点制〔试炼镐〕)
func _attune_charge_safe_row() -> Dictionary: func _attune_charge_safe_row() -> Dictionary:
for iid_v in attunement: for iid_v in _facts.attunement:
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v)) var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v))
if (arow.get("effect_kinds", []) as Array).has("ap_charge_safe"): if (arow.get("effect_kinds", []) as Array).has("ap_charge_safe"):
return arow return arow
@@ -200,9 +186,9 @@ func _attune_charge_safe_row() -> Dictionary:
# (used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner # (used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner
# set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面) # set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面)
func _reseed_attune_charges(reset: String) -> void: func _reseed_attune_charges(reset: String) -> void:
if _attune_loadout() == null or treasure_inventory == null: if _attune_loadout() == null or _reward_grant.treasure_inventory == null:
return # 直构零装配/库存未就位——充能回满跳过(空装配同语义) return # 直构零装配/库存未就位——充能回满跳过(空装配同语义)
_attune_loadout().reset_cycle(reset, treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体) _attune_loadout().reset_cycle(reset, _reward_grant.treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体)
# 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行: # 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行:
@@ -213,18 +199,13 @@ func attune_convert_state() -> Dictionary:
if row.is_empty() or String(row.get("form", "")) != "active_charged": if row.is_empty() or String(row.get("form", "")) != "active_charged":
return {"assembled": false, "points": 0, "need": 0, "display_name": "", "item_id": ""} return {"assembled": false, "points": 0, "need": 0, "display_name": "", "item_id": ""}
var cn := int(row.get("charge_n", 0)) var cn := int(row.get("charge_n", 0))
return {"assembled": true, "points": _attune_safe_seen % cn if cn > 0 else 0, "need": cn, return {"assembled": true, "points": _facts.attune_safe_seen % cn if cn > 0 else 0, "need": cn,
"display_name": String(row.get("display_name", "")), "item_id": String(row.get("item_id", ""))} "display_name": String(row.get("display_name", "")), "item_id": String(row.get("item_id", ""))}
# 特性求值(效果行为=数据行 triggers 订阅,效果引擎 dispatch 分发; # 特性求值(效果行为=数据行 triggers 订阅,效果引擎 dispatch 分发;
# 流派档位质变=FeatsEngine 纯函数求值 # 流派档位质变=FeatsEngine 纯函数求值
# (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 rider+1 / 档二 rider 可暴击) # (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 rider+1 / 档二 rider 可暴击)
var _seal_miss_seen := {} # 封印未中行去重(每安全格实例一次)
var _perfect_hit_true := 0 # 整图完美封印计数(install 重置)
var _perfect_hit_false := 0
var _perfect_forgiven := 0 # 完美保险豁免累计(单向累加自有账;契约 2 禁改写 _perfect_hit_false)
var _hero_atk := 0 # 主角攻击基值快照(encounter.hero_atk;E4 收口——表值运行期不可变)
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
@@ -234,6 +215,7 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
minesweeper = MinesweeperRules.new(db, action_economy) minesweeper = MinesweeperRules.new(db, action_economy)
_enemy_system = UnitContainer.new(db) _enemy_system = UnitContainer.new(db)
_status_effects = StatusContainer.new(db) _status_effects = StatusContainer.new(db)
_facts = BattleFacts.new() # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4)
_reward_economy = RewardEconomy.new(db, rng) _reward_economy = RewardEconomy.new(db, rng)
_reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线) _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_damage = DamageController.new(db, rng, _enemy_system, _status_effects) _damage = DamageController.new(db, rng, _enemy_system, _status_effects)
@@ -252,11 +234,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
minesweeper.round_end_board.connect(_on_round_end) minesweeper.round_end_board.connect(_on_round_end)
action_economy.round_end_stopped.connect(_on_round_end) action_economy.round_end_stopped.connect(_on_round_end)
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕) # 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕)
_grant_seg = GrantSegment.new(self, _fx_host, _reward_grant) # 结算五段接线随轮循环驱动件迁驻(round_cycle.gd——段件持有/段序/重入闸/摇号单点;构造序禁环不变)
_aura_seg = AuraSegment.new(self) _rounds = RoundCycle.new(self, _fx_host, _reward_grant)
_combat_seg = CombatSegment.new(self, _fx_host, _reward_grant)
_decay_seg = DecaySegment.new(self)
_verdict_seg = VerdictSegment.new(self)
# ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)---- # ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)----
@@ -269,7 +248,7 @@ func begin_battle(enc: Dictionary) -> bool:
return false return false
if not _install_and_reset(): if not _install_and_reset():
return false return false
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险) _facts.reset_round() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
_begin_battle_fx_state(enc) _begin_battle_fx_state(enc)
_begin_battle_attune_params(enc) _begin_battle_attune_params(enc)
_begin_battle_preview_economy(enc) _begin_battle_preview_economy(enc)
@@ -281,14 +260,14 @@ func begin_battle(enc: Dictionary) -> bool:
if not action_economy.notify_round_start(): if not action_economy.notify_round_start():
_fatal("行动经济 首轮 notify_round_start 被拒") _fatal("行动经济 首轮 notify_round_start 被拒")
return false return false
_roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读) _rounds.roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
round_started.emit(round_index) round_started.emit(round_index)
return true return true
# 受理段:encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。 # 受理段:_facts.encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。
func _begin_battle_accept(enc: Dictionary) -> bool: func _begin_battle_accept(enc: Dictionary) -> bool:
encounter.assign(enc) _facts.encounter.assign(enc)
_reward_grant.reset_battle() # 战斗域零 GP 持久(每场战斗缓冲从零起算;案C 闸复位) _reward_grant.reset_battle() # 战斗域零 GP 持久(每场战斗缓冲从零起算;案C 闸复位)
_damage.set_hero_ac_override(int(enc.get("hero_armor_class", -1))) # 主角护甲合流入参(伤害控制器 defender_ac 消费) _damage.set_hero_ac_override(int(enc.get("hero_armor_class", -1))) # 主角护甲合流入参(伤害控制器 defender_ac 消费)
db.set_reload_gate(true) db.set_reload_gate(true)
@@ -299,9 +278,9 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
if dice.is_empty(): if dice.is_empty():
_fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))]) _fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))])
return false return false
# E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径) # E4 收口:加成经 _facts.encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径;
_hero_atk = int(enc.get("hero_atk", int(db.combat.hero.atk))) # _hero_atk 派生读=受理后不变量,无快照成员)
if not _enemy_system.spawn({"level_index": int(enc.level_index), "node_depth": int(enc.node_depth), "hero_hp": int(enc.hero_hp), "unit_count_overrides": dice[0], "hero_atk_override": _hero_atk, "hero_max_hp_override": int(enc.get("hero_max_hp", -1)), "enemy_atk_overrides": dice[1], "hero_attack_type": String(enc.get("attack_type", "")), "hero_armor_type": String(enc.get("armor_type", "")), "row_kind": String(enc.get("node_type", "battle"))}): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss) if not _enemy_system.spawn({"level_index": int(enc.level_index), "node_depth": int(enc.node_depth), "hero_hp": int(enc.hero_hp), "unit_count_overrides": dice[0], "hero_atk_override": _hero_atk(), "hero_max_hp_override": int(enc.get("hero_max_hp", -1)), "enemy_atk_overrides": dice[1], "hero_attack_type": String(enc.get("attack_type", "")), "hero_armor_type": String(enc.get("armor_type", "")), "row_kind": String(enc.get("node_type", "battle"))}): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss)
_fatal("敌人系统 spawn 被拒") _fatal("敌人系统 spawn 被拒")
return false return false
return true return true
@@ -309,7 +288,7 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
# 遭遇骰(难度曲线与Boss GDD Core Rule 8):行有 unit_count_max 时按 [unit_count, max] 均匀骰; # 遭遇骰(难度曲线与Boss GDD Core Rule 8):行有 unit_count_max 时按 [unit_count, max] 均匀骰;
# 行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值 # 行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值
# encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径); # _facts.encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径);
# 敌人系统零随机纪律保持(收 override 即确定值)。 # 敌人系统零随机纪律保持(收 override 即确定值)。
# 返回 [count_overrides, atk_overrides];行集为空 = 返回 [](fail 判据交受理段 _fatal)。 # 返回 [count_overrides, atk_overrides];行集为空 = 返回 [](fail 判据交受理段 _fatal)。
func _begin_battle_dice(enc: Dictionary) -> Array: func _begin_battle_dice(enc: Dictionary) -> Array:
@@ -352,12 +331,12 @@ func _begin_battle_dice_roll(rows: Array, estream: RandomNumberGenerator, roll_a
# 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零) # 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零)
# 扩签运行态就位(归 fx_host)+账本挂接+桶挂引用+揭示/AC 两回调+rider 档位恒零+订阅重建。 # 扩签运行态就位(归 fx_host)+账本挂接+桶挂引用+揭示/AC 两回调+rider 档位恒零+订阅重建。
func _begin_battle_fx_state(enc: Dictionary) -> void: func _begin_battle_fx_state(enc: Dictionary) -> void:
# 同调装配运行态注入(enc.attunement 携带;off=空集零扰动。淬毒 N 注入 # 同调装配运行态注入(enc._facts.attunement 携带;off=空集零扰动。淬毒 N 注入
# 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install) # 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install)
attunement = (enc.get("attunement", []) as Array).duplicate() _facts.attunement = (enc.get("attunement", []) as Array).duplicate()
_attune_safe_seen = 0 _facts.attune_safe_seen = 0
_fx_host.reset_battle_state(int(enc.get("hero_armor_class", -1))) _fx_host.reset_battle_state(int(enc.get("hero_armor_class", -1)))
_mines_hits = {"total": 0, "last": 0, "round": 0} # 踩雷计数开战清零(事实面生命周期同款) _rounds.mines_hits = {"total": 0, "last": 0, "round": 0} # 踩雷计数开战清零(事实面生命周期同款)
var adv_bucket: Variant = enc.get("adv_bucket", null) var adv_bucket: Variant = enc.get("adv_bucket", null)
_fx_host.mount_buckets(adv_bucket if adv_bucket is Dictionary else {}, enc.get("level_counters")) # 冒险桶宿主字典(直构兜底自有空字典);关卡桶 LevelRunner 每关 new 下传、非 Dictionary 即不挂 _fx_host.mount_buckets(adv_bucket if adv_bucket is Dictionary else {}, enc.get("level_counters")) # 冒险桶宿主字典(直构兜底自有空字典);关卡桶 LevelRunner 每关 new 下传、非 Dictionary 即不挂
_reward_grant.bind_battle(_battle_hooks()) # 授予件战斗域回调束(充能消耗/真视计数/踩雷计数) _reward_grant.bind_battle(_battle_hooks()) # 授予件战斗域回调束(充能消耗/真视计数/踩雷计数)
@@ -368,13 +347,13 @@ func _begin_battle_fx_state(enc: Dictionary) -> void:
# 不设档位机制」):rider_tier 两注入恒零,散件单体效果=唯一形态 # 不设档位机制」):rider_tier 两注入恒零,散件单体效果=唯一形态
_damage.inject["rider_tier_bonus"] = 0 _damage.inject["rider_tier_bonus"] = 0
_damage.inject["rider_tier_crit"] = false _damage.inject["rider_tier_crit"] = false
_feat_first_dig_used = false _facts.feat_first_dig_used = false
# 树 rule 键消费(Fork 激活侧效果=效果引擎合成 # 树 rule 键消费(Fork 激活侧效果=效果引擎合成
# 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1)。其余 rule 键 # 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1)。其余 rule 键
# =stat 面投影不入战斗(T8 表内容 provisional 口径披露)) # =stat 面投影不入战斗(T8 表内容 provisional 口径披露))
var tree_rules: Array[Variant] = enc.get("tree_rules", []) as Array var tree_rules: Array[Variant] = enc.get("tree_rules", []) as Array
# 效果引擎订阅重建(装载序=feats 表序→treasure 表序→树规则,写死保种子复放——细则见 battle_fx_host.rebuild_subscriptions) # 效果引擎订阅重建(装载序=feats 表序→treasure 表序→树规则,写死保种子复放——细则见 battle_fx_host.rebuild_subscriptions)
_fx_host.rebuild_subscriptions(feat_ids, attunement, tree_rules) _fx_host.rebuild_subscriptions(feat_ids, _facts.attunement, tree_rules)
# 秘宝常时参数注入(装配集 kind 分发:远程镐挖距放宽/信标网络封印可达豁免; # 秘宝常时参数注入(装配集 kind 分发:远程镐挖距放宽/信标网络封印可达豁免;
@@ -415,19 +394,19 @@ func _begin_battle_preview_economy(enc: Dictionary) -> void:
# 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。 # 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。
func _install_and_reset(during_round := false) -> bool: func _install_and_reset(during_round := false) -> bool:
var installer := BoardInstaller.new({"db": db, "rng": rng, "minesweeper": minesweeper, var installer := BoardInstaller.new({"db": db, "rng": rng, "minesweeper": minesweeper,
"reward_economy": _reward_economy, "encounter": encounter, "fatal_cb": _fatal}) "reward_economy": _reward_economy, "encounter": _facts.encounter, "fatal_cb": _fatal})
if not installer.install(during_round): if not installer.install(during_round):
return false return false
_seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8) _facts.seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_seal_miss_seen.clear() _facts.seal_miss_seen.clear()
_perfect_hit_true = 0 # 整图完美计数随 install 重置 _facts.perfect_hit_true = 0 # 整图完美计数随 install 重置
_perfect_hit_false = 0 _facts.perfect_hit_false = 0
_perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零) _facts.perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零)
_board_from_midround_refresh = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false _facts.board_from_midround = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false
_board_seq += 1 _facts.board_seq += 1
board_changed.emit() board_changed.emit()
if treasure_inventory != null: if _reward_grant.treasure_inventory != null:
treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还) _reward_grant.treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还)
return true return true
@@ -448,9 +427,9 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if String(evt.get("kind", "")) == "safe": if String(evt.get("kind", "")) == "safe":
var cs_row: Dictionary[Variant, Variant] = _attune_charge_safe_row() var cs_row: Dictionary[Variant, Variant] = _attune_charge_safe_row()
if not cs_row.is_empty(): if not cs_row.is_empty():
_attune_safe_seen += 1 _facts.attune_safe_seen += 1
var cn_b: int = int(cs_row.get("charge_n", 999)) var cn_b: int = int(cs_row.get("charge_n", 999))
if cn_b > 0 and _attune_safe_seen % cn_b == 0: if cn_b > 0 and _facts.attune_safe_seen % cn_b == 0:
action_economy.grant_ap(int(cs_row.get("magnitude", 1))) action_economy.grant_ap(int(cs_row.get("magnitude", 1)))
if bool(evt.get("resource", false)): if bool(evt.get("resource", false)):
# 身份快照随缓冲捕获(批次隔离——轮内刷新后 mapping 已换批,授予必须用缓冲期身份, # 身份快照随缓冲捕获(批次隔离——轮内刷新后 mapping 已换批,授予必须用缓冲期身份,
@@ -459,47 +438,20 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if snap != null: if snap != null:
evt = evt.duplicate() evt = evt.duplicate()
evt["reward_snapshot"] = snap evt["reward_snapshot"] = snap
_facts_reveals.append(evt) _facts.reveals.append(evt)
func _on_seal_judged(evt: Dictionary) -> void: func _on_seal_judged(evt: Dictionary) -> void:
_facts_seals.append(evt) _facts.seals.append(evt)
func _on_round_end(_payload: Dictionary) -> void: func _on_round_end(_payload: Dictionary) -> void:
_facts_path = String(_payload.get("path_tag", "")) _facts.path = String(_payload.get("path_tag", ""))
_maybe_settle() _rounds.settle_if_due()
func _maybe_settle() -> void:
if _settling or battle_result != "":
return
if action_economy.phase != action_economy.PHASE_WAITING:
return
_settle()
# ---- 每轮结算序列(五段——段序骨架冻结于 SettlementTable.segment_order)----
func _settle() -> void:
_settling = true
var ri := round_index
# 段序编排(五段——段体归 game/core/settlement/,本类只做段序+行发射/开单原语单点)
_grant_seg.run(ri) # ① 授予段(案C 闸随段装载)
_aura_seg.run(ri) # ② 增益/减益段(续存实例组合行)
_combat_seg.run(ri) # ③ 战斗段(主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕)
_decay_seg.run(ri) # ④ 衰减段(恰一次恒执行含末轮——先兑付后衰减)
var verdict: String = _verdict_seg.run(ri) # ⑤ 判定段(防御性优先级 hero 先判)
if verdict != "continue":
return # 终局:_settling 不回置——_battle_over 已接管
_settling = false
# ---- 授予段 helpers(极性路由)----
# 开格产出乘区(贪婪矿灯/矿脉直觉/赌徒的血签事件收集器注入):
# positive=power ×(1+静态 mult%);negative=power ×(1+cost%)。
# 全零收集器=原快照原样返回(零扰动)。
func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary: func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary:
if positive and pos_m <= 0: if positive and pos_m <= 0:
return snap return snap
@@ -556,57 +508,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。 # 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。 # 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
# 摇定后、兑现前死亡 → 本拍技能作废。 # 摇定后、兑现前死亡 → 本拍技能作废。
func _roll_round_actions() -> void:
var pity_m: int = int(db.combat.skill.pity_threshold)
# facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts,
# 容器内直读主角行)
var facts := {
"round_index": round_index,
"last_round_end": "none" if action_economy.last_round_end().is_empty() else String(action_economy.last_round_end().get("path_tag", "none")),
"mines_hit_last": int(_mines_hits["last"]),
"mines_hit_total": int(_mines_hits["total"]),
}
for u in _enemy_system.query_alive("enemy"):
# Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行
# 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
for sid in _enemy_system.check_trigger_unlocks(u, facts):
var srow_u: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(sid)
_emit_row({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"},
round_index, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))],
0, {"skill_id": sid})
# enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射,
# 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值)
var pct_v := 0
for pe in (u.get("pool", []) as Array):
var pe_d: Dictionary[Variant, Variant] = pe
if String(pe_d.get("skill_id", "")) == sid and (pe_d.get("trigger", {}) as Dictionary).has("value"):
pct_v = int((pe_d.get("trigger", {}) as Dictionary).get("value", 0))
break
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
var eht_hits := _fx_host.fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v,
"is_boss": String(encounter.get("node_type", "")) == "boss"})
for h in eht_hits:
if String(h.get("kind", "")) == "target_attack_bonus":
u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0))
var entries: Array[Variant] = _enemy_system.rollable_skills(u, facts)
if _enemy_system.basic_streak(u) >= pity_m:
var specials: Array[Variant] = _enemy_system.special_skills(u, facts)
if not specials.is_empty():
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
var stream: RandomNumberGenerator = rng.open_stream("behavior_roll")
_enemy_system.set_pending_action(u, SkillLottery.draw(entries, stream))
_enemy_system.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
_mines_hits["last"] = _mines_hits["round"] # 轮次真正结束转存(轮内部分结算不更新)
_mines_hits["round"] = 0
# ---- 战斗段/算术读数(段体与掷点/折扣/暴击/克制/池乘区/下限/交付管线归 damage_controller.gd
# 与 settlement/combat_segment.gd;本类只保留薄委托——_defender_ac 供结算/呈现快照消费)----
# 防守方护甲等级读数——实现归 伤害控制器 defender_ac(本委托供结算快照/呈现快照)
func _defender_ac(unit: Dictionary) -> int: func _defender_ac(unit: Dictionary) -> int:
return _damage.defender_ac(unit) return _damage.defender_ac(unit)
@@ -643,12 +545,12 @@ func _emit_settlement_snapshot(verdict: String) -> void:
buffs.append({"id": eff.effect_id, "side": "enemy", "layers": eff.layers, "left": eff.remaining_settlements}) buffs.append({"id": eff.effect_id, "side": "enemy", "layers": eff.layers, "left": eff.remaining_settlements})
var mines_hit := 0 var mines_hit := 0
var safe_n := 0 var safe_n := 0
for evt in _facts_reveals: for evt in _facts.reveals:
if evt.kind == "mine": if evt.kind == "mine":
mines_hit += 1 mines_hit += 1
elif evt.kind == "safe": elif evt.kind == "safe":
safe_n += 1 safe_n += 1
var board_stats := {"seq": _board_seq} var board_stats := {"seq": _facts.board_seq}
if minesweeper.current_board() != null: if minesweeper.current_board() != null:
board_stats["w"] = minesweeper.current_board().width board_stats["w"] = minesweeper.current_board().width
board_stats["h"] = minesweeper.current_board().height board_stats["h"] = minesweeper.current_board().height
@@ -656,18 +558,18 @@ func _emit_settlement_snapshot(verdict: String) -> void:
board_stats["seals"] = minesweeper.seal_count() board_stats["seals"] = minesweeper.seal_count()
settlement_snapshot.emit({ settlement_snapshot.emit({
"kind": "settlement", "kind": "settlement",
"level": int(encounter.get("level_index", 0)), "level": int(_facts.encounter.get("level_index", 0)),
"depth": int(encounter.get("node_depth", 0)), "depth": int(_facts.encounter.get("node_depth", 0)),
"round": round_index, "round": round_index,
"verdict": verdict, "verdict": verdict,
"unit_count": enemies.size(), "unit_count": enemies.size(),
"spawn_count": _enemy_system.all_enemies().size(), # 本战斗实例化总数(含死亡残位)——unit_count=0 时区分「首轮全灭」vs「空场」 "spawn_count": _enemy_system.all_enemies().size(), # 本战斗实例化总数(含死亡残位)——unit_count=0 时区分「首轮全灭」vs「空场」
"hero": {"hp": int(_enemy_system.hero_unit().get("hp", 0)), "max_hp": int(_enemy_system.hero_unit().get("max_hp", 0)), "atk": int(_enemy_system.hero_unit().get("atk", 0)), "armor_class": _defender_ac(_enemy_system.hero_unit())}, "hero": {"hp": int(_enemy_system.hero_unit().get("hp", 0)), "max_hp": int(_enemy_system.hero_unit().get("max_hp", 0)), "atk": int(_enemy_system.hero_unit().get("atk", 0)), "armor_class": _defender_ac(_enemy_system.hero_unit())},
"economy": {"phase": String(action_economy.phase), "ap": int(action_economy.ap), "rap": int(action_economy.rap), "refresh_used": _refresh_used_this_round}, "economy": {"phase": String(action_economy.phase), "ap": int(action_economy.ap), "rap": int(action_economy.rap), "refresh_used": _facts.refresh_used},
"board": board_stats, "board": board_stats,
"enemies": enemies, "enemies": enemies,
"buffs": buffs, "buffs": buffs,
"round_facts": {"path": _facts_path, "safe": safe_n, "mines": mines_hit, "seal_events": _facts_seals.size(), }, "round_facts": {"path": _facts.path, "safe": safe_n, "mines": mines_hit, "seal_events": _facts.seals.size(), },
}) })
@@ -675,7 +577,7 @@ func _emit_settlement_snapshot(verdict: String) -> void:
func _exit_distribution() -> void: func _exit_distribution() -> void:
var mode := "same_board" var mode := "same_board"
if _facts_path == "cleared": if _facts.path == "cleared":
mode = "new_board" # cleared 轮恒走 new_board(契约义务);非清图轮恒同图(等待期刷新通道已移除) mode = "new_board" # cleared 轮恒走 new_board(契约义务);非清图轮恒同图(等待期刷新通道已移除)
if mode == "new_board": if mode == "new_board":
_reward_economy.retire_mapping() _reward_economy.retire_mapping()
@@ -689,32 +591,23 @@ func _exit_distribution() -> void:
round_index += 1 round_index += 1
_fx_host.emit_round_start() _fx_host.emit_round_start()
action_economy.notify_round_start() action_economy.notify_round_start()
_reset_round_facts() _facts.reset_round()
_refresh_used_this_round = 0 _facts.refresh_used = 0
_roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道) _rounds.roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
round_started.emit(round_index) round_started.emit(round_index)
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道 # 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
func can_refresh() -> bool: func can_refresh() -> bool:
return battle_result == "" \ return BattleRefreshFlow.can_refresh(self)
and _refresh_used_this_round < 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)
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。 # 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
# 挖掘期唯一通道:Active 相位立即换图 # 挖掘期唯一通道:Active 相位立即换图继续挖(RAP 按折后价扣;AP 保留、相位不动、不触发结算);
# 继续挖(RAP -8;AP 保留、相位不动、不触发结算);非挖掘期请求一律拒绝——非清图轮恒同图。 # 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
func player_request_refresh() -> bool: func player_request_refresh() -> bool:
if not can_refresh(): return BattleRefreshFlow.execute(self)
return false
if not action_economy.request_refresh():
return false
_refresh_used_this_round += 1
_reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
minesweeper.retire_board(true) # 旧板退役(轮内通道)
return _install_and_reset(true) # 当场换图——失败即编排违例 fail-fast
func _battle_over(result: String) -> void: func _battle_over(result: String) -> void:
@@ -737,11 +630,11 @@ func _battle_over(result: String) -> void:
_reward_economy.retire_mapping() _reward_economy.retire_mapping()
_enemy_system.despawn_all() _enemy_system.despawn_all()
_status_effects.notify_battle_end() _status_effects.notify_battle_end()
if treasure_inventory != null: if _reward_grant.treasure_inventory != null:
treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗) _reward_grant.treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗)
action_economy.notify_combat_end() # → Idle,RAP 清零 action_economy.notify_combat_end() # → Idle,RAP 清零
db.set_reload_gate(false) db.set_reload_gate(false)
_reset_round_facts() _facts.reset_round()
battle_finished.emit({"result": result, "hero_hp": hero_hp_left, "gp_dropped": gp_dropped}) battle_finished.emit({"result": result, "hero_hp": hero_hp_left, "gp_dropped": gp_dropped})
@@ -749,7 +642,7 @@ func _battle_over(result: String) -> void:
# gp_dropped = gp_drop 封闭流掷定(tier 行的 roll_min..roll_max 均匀整数——每战斗末恰一 scope) # gp_dropped = gp_drop 封闭流掷定(tier 行的 roll_min..roll_max 均匀整数——每战斗末恰一 scope)
# + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。 # + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。
func _roll_gp_drop() -> int: func _roll_gp_drop() -> int:
var tier := "boss" if String(encounter.get("node_type", "")) == "boss" else "normal" var tier := "boss" if String(_facts.encounter.get("node_type", "")) == "boss" else "normal"
var drops: Dictionary[Variant, Variant] = db.run.shop.gp_drop_row(tier) var drops: Dictionary[Variant, Variant] = db.run.shop.gp_drop_row(tier)
var roll := 0 var roll := 0
if drops.is_empty(): if drops.is_empty():
@@ -757,11 +650,11 @@ func _roll_gp_drop() -> int:
else: else:
var stream: RandomNumberGenerator = rng.open_stream("gp_drop") var stream: RandomNumberGenerator = rng.open_stream("gp_drop")
roll = stream.randi_range(int(drops.get("roll_min", 0)), int(drops.get("roll_max", 0))) roll = stream.randi_range(int(drops.get("roll_min", 0)), int(drops.get("roll_max", 0)))
var drop := roll + db.run.shop.level_gp_bonus_at(int(encounter.get("level_index", 0))) var drop := roll + db.run.shop.level_gp_bonus_at(int(_facts.encounter.get("level_index", 0)))
# 财富之道·聚(战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕, # 财富之道·聚(战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕,
# 店内 escrow 不乘〔范围=仅战斗掉落渠道〕;本函数返回值=结算框「GP 增量」行 # 店内 escrow 不乘〔范围=仅战斗掉落渠道〕;本函数返回值=结算框「GP 增量」行
# 显示源,落在入账前=显示即实得;乘数 provisional 150%) # 显示源,落在入账前=显示即实得;乘数 provisional 150%)
if (encounter.get("tree_rules", []) as Array).has("economy_hoard"): if (_facts.encounter.get("tree_rules", []) as Array).has("economy_hoard"):
drop = int(round(float(drop) * 1.5)) drop = int(round(float(drop) * 1.5))
return drop + _reward_grant.escrow return drop + _reward_grant.escrow
@@ -812,7 +705,7 @@ func presentation_snapshot() -> Dictionary:
"phase": String(action_economy.phase), "phase": String(action_economy.phase),
"ap": int(action_economy.ap), "rap": int(action_economy.rap), "ap": int(action_economy.ap), "rap": int(action_economy.rap),
"can_refresh": can_refresh(), "can_refresh": can_refresh(),
"refresh_used": _refresh_used_this_round, # 受理计数入呈现成文面(HUD 余量单源读此处) "refresh_used": _facts.refresh_used, # 受理计数入呈现成文面(HUD 余量单源读此处)
"hero": hero, "hero": hero,
"enemies": enemies, "enemies": enemies,
"hero_effects": _status_effects.query_active("hero").duplicate(true), # 值拷贝出户(快照消费方直改不渗入状态容器) "hero_effects": _status_effects.query_active("hero").duplicate(true), # 值拷贝出户(快照消费方直改不渗入状态容器)
@@ -839,12 +732,12 @@ func diagnostic_snapshot() -> Dictionary:
return { return {
"battle_result": battle_result, "battle_result": battle_result,
"round": round_index, "round": round_index,
"settling": _settling, "settling": _rounds.is_settling(),
"refresh_used_round": _refresh_used_this_round, "refresh_used_round": _facts.refresh_used,
"board_seq": _board_seq, "board_seq": _facts.board_seq,
"facts_path": _facts_path, "facts_path": _facts.path,
"facts_reveals": _facts_reveals.size(), "facts_reveals": _facts.reveals.size(),
"facts_seals": _facts_seals.size(), "facts_seals": _facts.seals.size(),
"action_economy": {"phase": String(action_economy.phase), "ap": action_economy.ap, "rap": action_economy.rap}, "action_economy": {"phase": String(action_economy.phase), "ap": action_economy.ap, "rap": action_economy.rap},
"minesweeper_board_installed": minesweeper.current_board() != null, "minesweeper_board_installed": minesweeper.current_board() != null,
"minesweeper_revealed": minesweeper.revealed_cells().size() if minesweeper.current_board() != null else 0, "minesweeper_revealed": minesweeper.revealed_cells().size() if minesweeper.current_board() != null else 0,
@@ -855,12 +748,6 @@ func diagnostic_snapshot() -> Dictionary:
} }
func _reset_round_facts() -> void:
_facts_reveals.clear()
_facts_seals.clear()
_facts_path = ""
# ---- 行协议(Core Rule:公共字段 + 行类型特有字段)---- # ---- 行协议(Core Rule:公共字段 + 行类型特有字段)----
func _emit_row(head: Dictionary, ri: int, display: String, value: int, extra: Dictionary) -> void: func _emit_row(head: Dictionary, ri: int, display: String, value: int, extra: Dictionary) -> void:
@@ -911,6 +798,6 @@ func _fatal(msg: String) -> void:
## 完美封印谓词(两账相减:有效误标=误标总数−豁免数) ## 完美封印谓词(两账相减:有效误标=误标总数−豁免数)
## 契约 2:_perfect_hit_false 保持纯净,豁免只累加 _perfect_forgiven,禁回退式改写与拦截式吞事件。 ## 契约 2:_facts.perfect_hit_false 保持纯净,豁免只累加 _facts.perfect_forgiven,禁回退式改写与拦截式吞事件。
func perfect_sweep_earned() -> bool: func perfect_sweep_earned() -> bool:
return _perfect_hit_false - _perfect_forgiven <= 0 and _perfect_hit_true >= 1 return _facts.perfect_hit_false - _facts.perfect_forgiven <= 0 and _facts.perfect_hit_true >= 1
+218
View File
@@ -0,0 +1,218 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 存档断点组装与回放唯一承载(AdventureRunner 拆件——单一变化原因=断点快照键面与回放策略):
## 断点快照拼盘、回放回填、读侧策略分发;BREAKPOINT_FIELDS 27 键登记单源随件(键名/策略标签/键序冻结)。
## 构造=引用束字典注入(db/rng/主角账三件/curio/stop/bank/loadout——件禁反向持宿主);
## 快照宿主输入(state/level_index/master_seed/runner/adventure_counters)逐调传参;
## resume_adventure 留宿主(壳+宿主侧回填语句——所有权缝分家,语句序逐步保形)。
class_name BreakpointStore
extends RefCounted
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表:
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
const BREAKPOINT_FIELDS := {
"level_index": {"strategy": "plain"},
"node_id": {"strategy": "plain"},
"hero_hp": {"strategy": "clamp"},
"bonus": {"strategy": "clamp"},
"gp": {"strategy": "plain"},
"shop_purchased": {"strategy": "plain"},
"shop_shelf": {"strategy": "plain"},
"shop_visit_buys": {"strategy": "plain"},
"attunement_charges": {"strategy": "keep_on_null"},
"graph_gen_v2": {"strategy": "plain"},
"master_seed": {"strategy": "plain"},
"rng_counter": {"strategy": "plain"},
"keystone": {"strategy": "plain"},
"visited_ids": {"strategy": "plain"},
"aux_items": {"strategy": "plain"},
"bonus_taken": {"strategy": "dual_merge"},
"bonus_deck": {"strategy": "keep_on_null"},
"relics": {"strategy": "plain"},
"bonus_pending": {"strategy": "token_migration"},
"feat_ticket_pity": {"strategy": "plain"},
"feat_ticket_sold": {"strategy": "plain"},
"adventure_counters": {"strategy": "keep_on_null"},
"vision_counter_used": {"strategy": "clamp"},
"refresh_stock": {"strategy": "clamp"},
"attune_refresh_y": {"strategy": "params_fallback"},
"bonus_result": {"strategy": "plain"},
"keystone_drops": {"strategy": "plain"},
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
}
# 引用束(宿主构造注入——成员名与宿主侧原名一致,迁移函数体逐字)
var db: ConfigDb
var rng: RngServiceCore
var _stats: HeroStats
var _wallet: GpWallet
var _journal: GainJournal
var _curio: CurioDeck = null # 令牌池件(池余库/获取计数随断点)
var _stop: CurioStop = null # 停点件(停点态/凭证计数片段)
var _refresh_bank: RefreshBank = null # 刷新账件(刷新存量/Y 池片段)
var _loadout: AttuneLoadout = null # 同调配装件(充能行集读侧)
# 断点域六成员(自宿主收编——宿主其余读写点经本件)
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
func _init(refs: Dictionary) -> void:
db = refs["db"]
rng = refs["rng"]
_stats = refs["stats"]
_wallet = refs["wallet"]
_journal = refs["journal"]
_curio = refs["curio"]
_stop = refs["stop"]
_refresh_bank = refs["bank"]
_loadout = refs["loadout"]
# 断点快照拼盘落盘(宿主 _snapshot_breakpoint 委托目标——宿主输入逐调传参)
func snapshot(state: String, level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> void:
if _save_manager == null or state != "running":
return
var parts: Dictionary[Variant, Variant] = _bp_parts(level_index, master_seed, runner, counters)
var bp: Dictionary[Variant, Variant] = {}
for key in BREAKPOINT_FIELDS.keys():
bp[key] = parts[key]
_save_manager.set_breakpoint(bp)
_save_manager.write_to_disk()
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
func _bp_parts(level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> Dictionary[Variant, Variant]:
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
var level_frag: Dictionary[Variant, Variant] = runner.breakpoint_fragment() if runner != null else {
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 runner 为 null——节点域按缺省落档
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
var parts: Dictionary[Variant, Variant] = {
"level_index": level_index,
"node_id": int(level_frag.get("node_id", -1)),
"hero_hp": int(stats_frag.get("hero_hp", 0)),
"bonus": journal_frag.get("bonus", {}),
"gp": int(wallet_frag.get("gp", 0)),
"shop_purchased": journal_frag.get("shop_purchased", {}),
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
"attunement_charges": (_loadout.breakpoint_fragment(runner.treasure_inventory) if runner != null and runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
"master_seed": master_seed,
"rng_counter": rng.counter(),
"keystone": int(journal_frag.get("keystone", 0)),
"visited_ids": level_frag.get("visited_ids", []),
"aux_items": level_frag.get("aux_items", []),
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
"adventure_counters": counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
}
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
var stray: Array[Variant] = []
for key in parts.keys():
if not BREAKPOINT_FIELDS.has(key):
stray.append(key)
if not stray.is_empty():
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
return parts
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
#(宿主侧账面语句——summary/levels_total/level_index/_pending_next_level——按语句序留宿主壳)
func restore_ledger(bp: Dictionary, params: Dictionary) -> void:
_stats.restore_fragment(bp, params)
_wallet.restore_fragment(bp, params)
_journal.restore_fragment(bp, params)
# 续玩三选一态回填(池与件计数归件、停点态归宿主;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
#(前置清理 _reset_curio_state 留宿主——桶归宿主字典;本函数承接其后的件侧还原语句,序逐步保形)
func restore_curio_state(bp: Dictionary) -> void:
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
for id in _curio.taken.keys():
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in bp_taken.keys():
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# relic 令牌 feat_id 随还原)
var bp_deck = _bp_read("bonus_deck", bp)
if bp_deck is Array:
var restored: Array[Variant] = []
for t in (bp_deck as Array):
var tok: Dictionary[Variant, Variant] = t as Dictionary
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
if String(rtok["kind"]) == "relic":
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
restored.append(rtok)
_curio.deck = restored
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
_stats.feats = []
for r in (bp.get("relics", []) as Array):
_stats.feats.append(String(r))
# 续玩参数面——数值账规范化与越界钳制(件侧;宿主侧四重绑定与预算注入按语句序夹在两件侧调用之间)
func apply_resume_params(params: Dictionary) -> void:
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
_stats.armor_type = String(params.get("armor_type", ""))
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
# 停点件/刷新账片段回填(凭证计数;刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
func restore_stop_bank_fragments(bp: Dictionary, params: Dictionary) -> void:
_stop.restore_fragment(bp, params)
_refresh_bank.restore_fragment(bp, params)
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
if not BREAKPOINT_FIELDS.has(key):
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
var v: Variant = bp.get(key, fallback)
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
return null # 类型不符——保持既有态(调用方跳过覆盖)
return v
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
func _bp_strategy(key: String) -> String:
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
match _bp_strategy(key):
"clamp":
return maxi(0, int(bp.get(key, fallback)))
"params_fallback":
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
_:
return int(bp.get(key, fallback))
+1
View File
@@ -0,0 +1 @@
uid://cw5qdd012h728
+19 -5
View File
@@ -1,8 +1,9 @@
## VERTICAL SLICE - NOT FOR PRODUCTION ## VERTICAL SLICE - NOT FOR PRODUCTION
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。 ## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。
## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行), ## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行),
## 表持有按域收进三容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域), ## 表持有按域收进四容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域/
## 消费面经 db.<容器>.<表> 直连。「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。 ## presentation 表现层布局域),消费面经 db.<容器>.<表> 直连。
## 「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
## 冒烟脚本可显式实例化本类(headless --script 亦会加载 autoload——共享 .tres 上的 resolver 归最后装载的实例); ## 冒烟脚本可显式实例化本类(headless --script 亦会加载 autoload——共享 .tres 上的 resolver 归最后装载的实例);
## 加载失败 is_ready=false 加 fatal 封印,调用方须查。 ## 加载失败 is_ready=false 加 fatal 封印,调用方须查。
class_name ConfigDb class_name ConfigDb
@@ -28,11 +29,14 @@ const TABLE_MANIFEST := {
"adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点) "adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点)
"feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动) "feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
"forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面 "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 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 run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge) var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
var presentation: ConfigDomainPresentation # 表现层布局域两表(battle_layout/camp_scene_layout)
var is_ready := false var is_ready := false
var _diagnostics: Array[Dictionary] = [] # {severity, category, message} var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
@@ -45,6 +49,7 @@ func _init() -> void:
combat = ConfigDomainCombat.new() combat = ConfigDomainCombat.new()
run = ConfigDomainRun.new() run = ConfigDomainRun.new()
crossrun = ConfigDomainCrossrun.new() crossrun = ConfigDomainCrossrun.new()
presentation = ConfigDomainPresentation.new()
_resolver = ConfigDbResolver.new(push_diag, _resolver = ConfigDbResolver.new(push_diag,
func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.difficulty) # 协作类持门面引用(诊断/致命通道回呼) func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.difficulty) # 协作类持门面引用(诊断/致命通道回呼)
@@ -117,7 +122,14 @@ func load_tables(root: String, collect_only: bool) -> bool:
if has_errors and not collect_only: if has_errors and not collect_only:
# 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建 # 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建
return false return false
# 容器装配(19 表成员按域收进三容器——2026-10-27 全清设计定稿) # 容器装配(表行按域收进四容器+resolver 注入)归私有协函数——load_tables 只留加载/校验/致命门面
_assign_loaded(loaded)
is_ready = not has_errors
return not has_errors
# 容器装配协函数:20 表成员按域收进四容器(2026-10-27 全清设计定稿,2026-10-31 增表现层布局域)
func _assign_loaded(loaded: Dictionary) -> void:
combat.board_gen = loaded.get("board_gen") combat.board_gen = loaded.get("board_gen")
combat.variation = loaded.get("variation") combat.variation = loaded.get("variation")
combat.status_effects = loaded.get("status_effects") combat.status_effects = loaded.get("status_effects")
@@ -137,12 +149,14 @@ func load_tables(root: String, collect_only: bool) -> bool:
crossrun.treasure_items = loaded.get("treasure_items") crossrun.treasure_items = loaded.get("treasure_items")
crossrun.feats = loaded.get("feats") crossrun.feats = loaded.get("feats")
crossrun.forge = loaded.get("forge") 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 文件与键零搬移) # resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
combat.board_gen._resolver = _resolver combat.board_gen._resolver = _resolver
combat.enemy._resolver = _resolver combat.enemy._resolver = _resolver
run.difficulty._resolver = _resolver run.difficulty._resolver = _resolver
is_ready = not has_errors
return not has_errors
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体: # 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
+7
View File
@@ -0,0 +1,7 @@
## 表现层布局域容器(D2 域容器——config_db 表持有按域收敛):战斗屏与营地场景布局两表类型化持有。
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainPresentation
extends Resource
var battle_layout: BattleLayoutTable
var camp_scene_layout: CampSceneLayoutTable
@@ -0,0 +1 @@
uid://bay1b1s3lrd1e
+28 -10
View File
@@ -460,8 +460,8 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型—— # 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output # 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。 # 池乘区 → 下限 → +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: func _resolve_enemy_skill(t: Dictionary) -> void:
var hero: Dictionary[Variant, Variant] = units.hero_unit() var hero: Dictionary[Variant, Variant] = units.hero_unit()
var roll := _enemy_roll(t) 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), # damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers),
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/ # 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供) # counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供)
var dt_hits := _fx_hits("damage_taken", {"kind": "enemy_skill", "amount": total, var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
"source": String(t.context.get("skill_id", "")), "round": int(t.round_index)}) total = int(dt_res["total"])
total = int(_enemy_shield_apply(dt_hits, total)["total"])
units.apply_damage(hero, 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)), 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)} "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} return {"roll": int(roll_read.roll), "threshold": threshold, "crit": crit, "discount": discount, "total": total}
# damage_taken 施加面(shield 加法族合计;damage_reduce 逐条减量〔背水〕;ac_bonus 转编排器单点 # 全局受伤发射单点(迭代 46 卡A T2——所有主角承伤路径唯一发射口:敌方攻击/地形/危险格/错误封印
# 〔石肤 AC 累积归 set_hero_ac_override 消费面〕;counter_add 施加)。返回 {total, shield}。 # 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
func _enemy_shield_apply(hits: Array, total: int) -> Dictionary: # 同一伤害单据重复发射由 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 var shield := 0
for h in hits: for h in hits:
match String(h.get("kind", "")): match String(h.get("kind", "")):
@@ -565,9 +577,15 @@ func _resolve_board_blast(t: Dictionary) -> void:
_outcomes[int(t.ticket_id)] = "redeemed" _outcomes[int(t.ticket_id)] = "redeemed"
# 棋盘主角直伤(反噬/治疗包负向/地形变异):无类型必中,下限保底;context 键并入行载荷 # 棋盘主角直伤(反噬/治疗包负向/危险格/错误封印资源直伤):无类型必中,下限保底;context 键并入行载荷。
# damage_taken 全局发射点(迭代 46 卡A T2——单据未标 hero_damage_consumed 时在下限前发射并消费一次;
# 地形路径已在授予段开单点发射并标记,此处跳过防重复)
func _resolve_board_flat_hero(t: Dictionary) -> void: 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) units.apply_damage(units.hero_unit(), dealt)
var extra: Dictionary[Variant, Variant] = {"target": "hero"} var extra: Dictionary[Variant, Variant] = {"target": "hero"}
for k in t.context.keys(): 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", 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", "armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle",
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed", "round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed",
"board_installed"] "board_installed",
# 换图系双事件(refresh_request=玩家请求刷新〔省力周转 apply_discount 订阅面——折扣只改门槛
# 与扣费不改拒绝判据〕;board_refreshed=成功刷新事实〔换图系七件订阅主挂点——装新板后才发射〕)
"refresh_request", "board_refreshed"]
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。 ## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件) ## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件+轮内刷新面〔换图系〕)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"] const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll",
"refresh_request", "board_refreshed"]
## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型: ## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、 ## 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", "mine_count_preview",
# forgive_seal_miss=完美保险(本场首次封印落空不计入完美 # forgive_seal_miss=完美保险(本场首次封印落空不计入完美
# 封印判定——订阅 seal_missed 的豁免效果,消费点在授予段;feats.tres 首例 domain=battle 行) # 封印判定——订阅 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 ## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕; ## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
+2 -2
View File
@@ -9,7 +9,7 @@
class_name LevelRunner class_name LevelRunner
extends RefCounted extends RefCounted
signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(三选一停点空载荷——内容经 present_node_curio 拉取) · rest={healed,hp,max_hp} · shop={gp} signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(三选一停点空载荷——内容经 present_node_curio 拉取) · rest={healed,hp,max_hp} · shop={gp}(pool 态加 shelf:true)
signal choices_presented(candidates: Array) ## 选路口(兑付确认后——候选节点引用数组,零自动推进) signal choices_presented(candidates: Array) ## 选路口(兑付确认后——候选节点引用数组,零自动推进)
signal step_row(row: Dictionary) ## 转发当前 战斗结算 结算行 signal step_row(row: Dictionary) ## 转发当前 战斗结算 结算行
signal round_started(round_index: int) signal round_started(round_index: int)
@@ -517,7 +517,7 @@ func _start_battle(node: Dictionary) -> bool:
_settlement = BattleOrchestrator.new(db, rng) _settlement = BattleOrchestrator.new(db, rng)
if treasure_inventory == null: if treasure_inventory == null:
treasure_inventory = TreasureItems.new(db) # 惰性建——关卡域单实例(跨战斗延续) treasure_inventory = TreasureItems.new(db) # 惰性建——关卡域单实例(跨战斗延续)
_settlement.treasure_inventory = treasure_inventory # 库存注入(授予路由+use_treasure 编排消费面) _settlement.reward_grant().treasure_inventory = self.treasure_inventory # 库存注入(经授予件出口直注——注入时序在宿主构造之后不变)
if _settlement != null: if _settlement != null:
_settlement.set_treasure_unlocked(_treasure_unlocked) # 解锁门控下传(装板摇号前) _settlement.set_treasure_unlocked(_treasure_unlocked) # 解锁门控下传(装板摇号前)
# core↔core 接线只许方法 Callable(静态规则 S6——lambda 捕获 self 会延长 # core↔core 接线只许方法 Callable(静态规则 S6——lambda 捕获 self 会延长
+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) 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: 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, 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
+2 -2
View File
@@ -26,7 +26,7 @@ func run(ri: int) -> void:
# 同一次 _settle 内,facts 已定。 # 同一次 _settle 内,facts 已定。
var round_safe := 0 var round_safe := 0
var round_mine := false var round_mine := false
for evt in oc._facts_reveals: for evt in oc._facts.reveals:
if evt.kind == "safe": if evt.kind == "safe":
round_safe += 1 round_safe += 1
elif evt.kind == "mine": elif evt.kind == "mine":
@@ -53,7 +53,7 @@ func _hero_strike(ri: int) -> void:
var hero_target = oc._enemy_system.default_target() var hero_target = oc._enemy_system.default_target()
if hero_alive and hero_target != null: if hero_alive and hero_target != null:
var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击", var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击",
{"channel": "combat", "base_value": oc._hero_atk, "target_rule": DamageController.TARGET_FRONT_ALIVE, {"channel": "combat", "base_value": oc._hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE,
"ri": ri, "origin_cell": Vector2i(-1, -1), "ri": ri, "origin_cell": Vector2i(-1, -1),
"attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))}) "attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))})
oc._damage.submit(ht) oc._damage.submit(ht)
+71 -45
View File
@@ -6,7 +6,8 @@ extends RefCounted
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)—— # 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。 # 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面 # 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 三位兑付块 # kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。 # 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
@@ -32,9 +33,30 @@ func run(ri: int) -> void:
_round_end_apply(ri) _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 减半负向兑付) # 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
func _reveal_batch(ri: int) -> void: func _reveal_batch(ri: int) -> void:
for evt in oc._facts_reveals: for evt in oc._facts.reveals:
if not bool(evt.get("resource", false)): if not bool(evt.get("resource", false)):
continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②) continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②)
var snap = evt.get("reward_snapshot") var snap = evt.get("reward_snapshot")
@@ -61,7 +83,7 @@ func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
if is_new_type: if is_new_type:
fx_host._fx_new_resource_types[ctype] = true fx_host._fx_new_resource_types[ctype] = true
var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri, var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri,
"is_first_dig_of_battle": not oc._feat_first_dig_used, "is_first_dig_of_battle": not oc._facts.feat_first_dig_used,
"terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array), "terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array),
"is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0 "is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0
or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1, or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1,
@@ -91,8 +113,8 @@ func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void:
if String(h.get("kind", "")) == "grant_material": if String(h.get("kind", "")) == "grant_material":
_apply_material_hit(h, evt.cell, ri) _apply_material_hit(h, evt.cell, ri)
var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell") var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell")
if not spell_hits.is_empty() and not oc._feat_first_dig_used: if not spell_hits.is_empty() and not oc._facts.feat_first_dig_used:
oc._feat_first_dig_used = true oc._facts.feat_first_dig_used = true
var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢", var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢",
{"channel": "board", "base_value": int((spell_hits[0] as Dictionary).get("value", 0)), {"channel": "board", "base_value": int((spell_hits[0] as Dictionary).get("value", 0)),
"target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": evt.cell, "attack_type": "magic"}) "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": evt.cell, "attack_type": "magic"})
@@ -160,50 +182,35 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径, # 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场) # 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
func _terrain_batch(ri: int) -> void: func _terrain_batch(ri: int) -> void:
for evt in oc._facts_reveals: for evt in oc._facts.reveals:
var vids: Array[Variant] = evt.get("variant_ids", []) var vids: Array[Variant] = evt.get("variant_ids", [])
for vid in vids: for vid in vids:
var vrow: Dictionary[Variant, Variant] = oc.db.combat.variation.row_by_id(String(vid)) var vrow: Dictionary[Variant, Variant] = oc.db.combat.variation.row_by_id(String(vid))
if String(vrow.get("effect", "")) != "flat_damage": if String(vrow.get("effect", "")) != "flat_damage":
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面) continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内 # 地形变异伤害单据——无类型必中,下限保底在控制器管线内
# damage_taken 发射在单据开立前:damage_reduce〔背水——hp_ratio when # damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken:
# 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加; # 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化) # hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
var dt_amount := int(vrow.get("effect_value", 0)) var dt_amount := int(vrow.get("effect_value", 0))
var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount, var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
"source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid), dt_amount = int(dt_res["total"])
"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 vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), 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, {"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) 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": if String(h.get("kind", "")) == "grant_material":
_apply_material_hit(h, evt.cell, ri) _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 批后统一开单:轮末每面封印命中对最前存活敌 # 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌—— # 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。 # 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
# 伤害单据案C 暂存战斗段末兑付。 # 伤害单据案C 暂存战斗段末兑付。
func _seal_batch(ri: int) -> void: func _seal_batch(ri: int) -> void:
for evt in oc._facts_seals: for evt in oc._facts.seals:
_judge_seal(evt, ri) _judge_seal(evt, ri)
_seal_strike_batch(ri) _seal_strike_batch(ri)
@@ -213,7 +220,7 @@ func _seal_batch(ri: int) -> void:
func _seal_strike_batch(ri: int) -> void: func _seal_strike_batch(ri: int) -> void:
var fs_hits := 0 var fs_hits := 0
var fs_misses := 0 var fs_misses := 0
for fevt in oc._facts_seals: for fevt in oc._facts.seals:
if bool(fevt.get("hit", false)): if bool(fevt.get("hit", false)):
fs_hits += 1 fs_hits += 1
else: else:
@@ -223,7 +230,7 @@ func _seal_strike_batch(ri: int) -> void:
var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses) var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses)
if strike.is_empty(): if strike.is_empty():
return return
for fevt in oc._facts_seals: for fevt in oc._facts.seals:
if not bool(fevt.get("hit", false)): if not bool(fevt.get("hit", false)):
continue continue
var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规", var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规",
@@ -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: func _round_end_apply(ri: int) -> void:
var re_fx := fx_host.fx_emit("round_end", {"round": ri}) var re_fx := fx_host.fx_emit("round_end", {"round": ri})
_round_end_blast_tickets(re_fx, 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: for h in re_fx:
if String(h.get("kind", "")) == "counter_add": if String(h.get("kind", "")) == "counter_add":
_apply_counter_hit(h) _apply_counter_hit(h)
@@ -272,12 +280,30 @@ func _round_end_apply(ri: int) -> void:
var rap_pct: int = oc.attune_param_sum("rap_transfer_mult") var rap_pct: int = oc.attune_param_sum("rap_transfer_mult")
oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"), oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"),
rap_pct if rap_pct > 0 else 100) rap_pct if rap_pct > 0 else 100)
if oc._facts_path == "cleared": if oc._facts.path == "cleared":
reward_grant.grant_clear_reward(ri, oc._board_from_midround_refresh, oc.perfect_sweep_earned()) reward_grant.grant_clear_reward(ri, oc._facts.board_from_midround, oc.perfect_sweep_earned())
fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入) fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) 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 用中性名, # round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付) # per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付)
func _round_end_blast_tickets(re_fx: Array, ri: int) -> void: func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
@@ -305,10 +331,10 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付) # 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付)
func _judge_seal_hit(evt: Dictionary, ri: int) -> void: func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
var cell: Vector2i = evt.cell var cell: Vector2i = evt.cell
if oc._seal_consumed.has(cell): if oc._facts.seal_consumed.has(cell):
return # 去重跳过;零明细行 return # 去重跳过;零明细行
oc._seal_consumed[cell] = true oc._facts.seal_consumed[cell] = true
oc._perfect_hit_true += 1 oc._facts.mark_perfect_hit()
var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri, var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri,
"terrain_kind": oc._harmful_adjacent(cell)}) "terrain_kind": oc._harmful_adjacent(cell)})
_seal_burst_tickets(fh_hits, cell, ri) _seal_burst_tickets(fh_hits, cell, ri)
@@ -367,8 +393,8 @@ func _seal_preview_area(cell: Vector2i, ri: int) -> void:
var n_snap = oc._reward_economy.query_full_snapshot(n) var n_snap = oc._reward_economy.query_full_snapshot(n)
if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))): if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))):
types.append(String((n_snap as Dictionary).get("cell_type", ""))) types.append(String((n_snap as Dictionary).get("cell_type", "")))
if oc.treasure_inventory != null and not types.is_empty(): if reward_grant.treasure_inventory != null and not types.is_empty():
oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}) oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types})
@@ -376,10 +402,10 @@ func _seal_preview_area(cell: Vector2i, ri: int) -> void:
# grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付) # grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付)
func _judge_seal_miss(evt: Dictionary, ri: int) -> void: func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
var cell: Vector2i = evt.cell var cell: Vector2i = evt.cell
oc._perfect_hit_false += 1 oc._facts.mark_perfect_miss()
if oc._seal_miss_seen.has(cell): if oc._facts.seal_miss_seen.has(cell):
return # 每格实例一次去重 return # 每格实例一次去重
oc._seal_miss_seen[cell] = true oc._facts.seal_miss_seen[cell] = true
# 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错 # 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错
# 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在 # 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在
# 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮 # 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮
@@ -391,11 +417,11 @@ func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
elif String(h.get("kind", "")) == "forgive_seal_miss": elif String(h.get("kind", "")) == "forgive_seal_miss":
# 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账 # 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账
# _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3)
oc._perfect_forgiven += 1 oc._facts.mark_perfect_forgiven()
_apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中)
oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0, oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0,
{"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven}) {"effect_id": "insurance_used", "forgiven": oc._facts.perfect_forgiven})
var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else "" var hl := "perfect_broken" if oc._facts.perfect_hit_false - oc._facts.perfect_forgiven == 1 else ""
oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {}) oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {})
var snap = oc._reward_economy.grant(cell) var snap = oc._reward_economy.grant(cell)
if not (snap is Dictionary): if not (snap is Dictionary):
+106
View File
@@ -0,0 +1,106 @@
## 轮循环驱动件:每轮结算周期的唯一驱动——结算重入门闸(settling)、五段段序执行
## (段序骨架冻结于 SettlementTable.segment_order)、开轮摇号(共识点 B:技能选择前置到轮次开始,
## 摇定即本拍结算的选技)、踩雷计数账(转存时点=摇号轮末)。
## 单一变化原因:轮结算周期的推进结构(段序、重入闸、摇号时序)。
## 耦合边界:oc=宿主引用(五段段类既定同款直调形态)——经 oc 取 facts/协作件/行发射/出口分发;
## 五段构造参数与持有形态不变(grant/combat 三引用、aura/decay/verdict 单引用);判定段零改动
## (_exit_distribution 留宿主);随机只经 oc.rng 的 behavior_roll 封闭流(流名、开流时点、取号序逐字);
## 零信号连接、零 await;随编排器每战新建同灭。
class_name RoundCycle
extends RefCounted
var oc: BattleOrchestrator # 宿主引用(段类同款直调——facts/协作件/行发射/出口分发取数单点)
var mines_hits: Dictionary[Variant, Variant] = {"total": 0, "last": 0, "round": 0} # 踩雷计数(本场累计/上轮转存/当轮累计三读数合一账)
var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点)
var _aura_seg = null # AuraSegment(②增益/减益段)
var _combat_seg = null # CombatSegment(③战斗段)
var _decay_seg = null # DecaySegment(④衰减段)
var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归宿主)
var _settling := false
func _init(host: BattleOrchestrator, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
oc = host
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕——五行自宿主 _init 逐字迁入)
_grant_seg = GrantSegment.new(oc, fx_host_ref, reward_grant_ref)
_aura_seg = AuraSegment.new(oc)
_combat_seg = CombatSegment.new(oc, fx_host_ref, reward_grant_ref)
_decay_seg = DecaySegment.new(oc)
_verdict_seg = VerdictSegment.new(oc)
# 结算重入门闸+段序体(原宿主 _maybe_settle 三闸与 _settle 合体——闸序 settling→battle_result→phase
# 逐字;终局早退不回置 _settling,_battle_over 接管)
func settle_if_due() -> void:
if _settling or oc.battle_result != "":
return
if oc.action_economy.phase != oc.action_economy.PHASE_WAITING:
return
_settling = true
var ri := oc.round_index
# 段序编排(五段——段体归 game/core/settlement/,宿主只做行发射/开单原语单点)
_grant_seg.run(ri) # ① 授予段(案C 闸随段装载)
_aura_seg.run(ri) # ② 增益/减益段(续存实例组合行)
_combat_seg.run(ri) # ③ 战斗段(主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕)
_decay_seg.run(ri) # ④ 衰减段(恰一次恒执行含末轮——先兑付后衰减)
var verdict: String = _verdict_seg.run(ri) # ⑤ 判定段(防御性优先级 hero 先判)
if verdict != "continue":
return # 终局:_settling 不回置——_battle_over 已接管
_settling = false
func is_settling() -> bool:
return _settling
# ---- 开轮摇号(共识点 B:技能选择前置到轮次开始,数值计算留结算段——原宿主函数逐字迁入)----
# 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能
# (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从统一流顺序取号摇定本拍动作。
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
# 摇定后、兑现前死亡 → 本拍技能作废。
func roll_round_actions() -> void:
var pity_m: int = int(oc.db.combat.skill.pity_threshold)
# facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts,
# 容器内直读主角行)
var facts := {
"round_index": oc.round_index,
"last_round_end": "none" if oc.action_economy.last_round_end().is_empty() else String(oc.action_economy.last_round_end().get("path_tag", "none")),
"mines_hit_last": int(mines_hits["last"]),
"mines_hit_total": int(mines_hits["total"]),
}
for u in oc._enemy_system.query_alive("enemy"):
# Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行
# 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
for sid in oc._enemy_system.check_trigger_unlocks(u, facts):
var srow_u: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(sid)
oc._emit_row({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"},
oc.round_index, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))],
0, {"skill_id": sid})
# enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射,
# 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值)
var pct_v := 0
for pe in (u.get("pool", []) as Array):
var pe_d: Dictionary[Variant, Variant] = pe
if String(pe_d.get("skill_id", "")) == sid and (pe_d.get("trigger", {}) as Dictionary).has("value"):
pct_v = int((pe_d.get("trigger", {}) as Dictionary).get("value", 0))
break
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
var eht_hits := oc._fx_host.fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v,
"is_boss": String(oc._facts.encounter.get("node_type", "")) == "boss"})
for h in eht_hits:
if String(h.get("kind", "")) == "target_attack_bonus":
u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0))
var entries: Array[Variant] = oc._enemy_system.rollable_skills(u, facts)
if oc._enemy_system.basic_streak(u) >= pity_m:
var specials: Array[Variant] = oc._enemy_system.special_skills(u, facts)
if not specials.is_empty():
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
var stream: RandomNumberGenerator = oc.rng.open_stream("behavior_roll")
oc._enemy_system.set_pending_action(u, SkillLottery.draw(entries, stream))
oc._enemy_system.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
mines_hits["last"] = mines_hits["round"] # 轮次真正结束转存(轮内部分结算不更新)
mines_hits["round"] = 0
+1
View File
@@ -0,0 +1 @@
uid://ce07smbypdcyj
+30
View File
@@ -0,0 +1,30 @@
## 战斗屏布局表 schema:分区几何与明细带高度的单行类型化读取(呈现层布局配置,非玩法数值)。
## 消费面 = BattleStageView / BattleLogView / HeroInfoPanelView / ApBarView(经 ConfigDb.presentation.battle_layout);
## 文档只写结构关系与表名,不复制数值。validate() 违例加载期致命(fail-fast)。
class_name BattleLayoutTable
extends Resource
@export var stage_h: int = 252 # B 区高(全宽战斗画面,含底部明细带)
@export var info_panel_w: int = 240 # C 区宽(左下角色信息列)
@export var ap_bar_h: int = 44 # D 区 AP 条高
@export var log_band_h_dig: int = 24 # 明细带挖掘期收起高(单行上轮摘要)
@export var log_band_h_settle: int = 84 # 明细带结算期展开高(实时结算流)
func validate() -> Array:
var errs: Array[Dictionary] = []
if stage_h < 120 or stage_h > 600:
errs.append({"category": "domain", "message": "stage_h 越域 [120,600]: %d" % stage_h})
if info_panel_w < 120 or info_panel_w > 480:
errs.append({"category": "domain", "message": "info_panel_w 越域 [120,480]: %d" % info_panel_w})
if ap_bar_h < 24 or ap_bar_h > 96:
errs.append({"category": "domain", "message": "ap_bar_h 越域 [24,96]: %d" % ap_bar_h})
if log_band_h_dig < 16 or log_band_h_dig > 80:
errs.append({"category": "domain", "message": "log_band_h_dig 越域 [16,80]: %d" % log_band_h_dig})
if log_band_h_settle < 24 or log_band_h_settle > 160:
errs.append({"category": "domain", "message": "log_band_h_settle 越域 [24,160]: %d" % log_band_h_settle})
if log_band_h_settle < log_band_h_dig:
errs.append({"category": "cross_field", "message": "log_band_h_settle(%d) < log_band_h_dig(%d)——展开态不得矮于收起态" % [log_band_h_settle, log_band_h_dig]})
if log_band_h_settle >= stage_h:
errs.append({"category": "cross_field", "message": "log_band_h_settle(%d) ≥ stage_h(%d)——明细带不得占满 B 区" % [log_band_h_settle, stage_h]})
return errs
@@ -0,0 +1 @@
uid://b3jh66yq4b7rp
@@ -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
+11
View File
@@ -0,0 +1,11 @@
[gd_resource type="Resource" script_class="BattleLayoutTable" load_steps=2 format=3]
[ext_resource type="Script" path="res://data/schemas/battle_layout_table.gd" id="1"]
[resource]
script = ExtResource("1")
stage_h = 252
info_panel_w = 240
ap_bar_h = 44
log_band_h_dig = 24
log_band_h_settle = 84
+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")]
+210
View File
@@ -869,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
}
}
]
} }
] ]
+1 -1
View File
@@ -7,7 +7,7 @@ extends RefCounted
## 三束按域收拢(u21——同 u20 形;views=视图句柄/getters=查询 Callable 活引用/flow=编排 Callable): ## 三束按域收拢(u21——同 u20 形;views=视图句柄/getters=查询 Callable 活引用/flow=编排 Callable):
## 原 12 参长签名收 4 参——字段即原构造参数逐一同名迁移,键面=消费方改址合同。 ## 原 12 参长签名收 4 参——字段即原构造参数逐一同名迁移,键面=消费方改址合同。
var db 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 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 束) 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 db
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面) var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面)
var _save_get: Callable # () → 存档承载现值(同上——营地 recent 摘要读数面) 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 shop_overlay: ShopOverlay
var toast: ToastStack var toast: ToastStack
var start_overlay: Control # 开始层互斥面(开合仿设置面先例——非模态且树序在后,规范 2.3) var start_overlay: Control # 开始层互斥面(开合仿设置面先例——非模态且树序在后,规范 2.3)
@@ -23,7 +23,7 @@ func _init(ctx: CampFlowContext) -> void:
db = ctx.db db = ctx.db
_meta_get = ctx.getters.meta_get _meta_get = ctx.getters.meta_get
_save_get = ctx.getters.save_get _save_get = ctx.getters.save_get
camp_overlay = ctx.views.camp_overlay views = ctx.views # 营地域五目标+刷新单口+关层收闭面(卡④ T3——CampOverlay 退役后投影目标改线)
shop_overlay = ctx.views.shop_overlay shop_overlay = ctx.views.shop_overlay
toast = ctx.views.toast toast = ctx.views.toast
start_overlay = ctx.views.start_overlay start_overlay = ctx.views.start_overlay
@@ -34,21 +34,54 @@ func _init(ctx: CampFlowContext) -> void:
_refresh_presentations = ctx.flow.refresh_presentations _refresh_presentations = ctx.flow.refresh_presentations
# 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后, # 打开营地场景根(进档落点——卡④ T3 改挂:开层=交互式场景图,四子界面经场景点选进)
# 规范 2.3 遮挡纪律;打开时先刷条件解锁——达标未解锁就地入账)
func open_camp() -> void: func open_camp() -> void:
var meta = _meta_get.call() var meta = _meta_get.call()
if meta == null: if meta == null:
return return
meta.refresh_treasure_unlocks() # 条件解锁刷新 meta.refresh_treasure_unlocks() # 条件解锁刷新
start_overlay.visible = false start_overlay.visible = false
_open_modal.call(camp_overlay) # 模态栈单口(登记层开合一律走栈) _open_modal.call(views.camp_scene) # 模态栈单口(场景根开——选档层被栈遮挡挂起)
camp_overlay.open_camp(camp_data()) views.refresh_bus.refresh(camp_data()) # 进档动线尾显式刷新一闸(卡⑤ 契约 5 时机②——HUD 读数重读)
_refresh_presentations.call() _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: 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 start_overlay.visible = true
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) _refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
@@ -79,7 +112,7 @@ func on_camp_buy(attr: String) -> void:
if not meta.buy_treasure_unlock(attr): if not meta.buy_treasure_unlock(attr):
toast.show_toast("购买被拒(余额不足或不可购买)", "warn") toast.show_toast("购买被拒(余额不足或不可购买)", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
return return
# Y 刷新升级行购买(双入口之一——树上 ks_refresh 同账; # Y 刷新升级行购买(双入口之一——树上 ks_refresh 同账;
@@ -87,7 +120,7 @@ func on_camp_buy(attr: String) -> void:
if not meta.buy_attune_refresh(): if not meta.buy_attune_refresh():
toast.show_toast("购买被拒", "warn") toast.show_toast("购买被拒", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
@@ -99,7 +132,7 @@ func on_camp_attune_toggle(item_id: String) -> void:
if not meta.toggle_attunement(item_id): if not meta.toggle_attunement(item_id):
toast.show_toast("同调位已满——先卸下再装配", "warn") toast.show_toast("同调位已满——先卸下再装配", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
# 天赋树购买(写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧) # 天赋树购买(写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧)
@@ -111,7 +144,7 @@ func on_camp_tree_buy(node_id: String) -> void:
if not ok: if not ok:
toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn") toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _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): if not meta.tree_toggle_fork(group, side):
toast.show_toast("切换被拒——未激活组请先购买", "warn") toast.show_toast("切换被拒——未激活组请先购买", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
@@ -136,7 +169,7 @@ func on_camp_forge_buy(route_id: String) -> void:
if not meta.forge_buy_level(route_id): if not meta.forge_buy_level(route_id):
toast.show_toast("锻造被拒——钥石不足或路线不可锻造", "warn") toast.show_toast("锻造被拒——钥石不足或路线不可锻造", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
@@ -148,7 +181,7 @@ func on_camp_forge_unlock(route_id: String) -> void:
if not meta.forge_buy_unlock(route_id): if not meta.forge_buy_unlock(route_id):
toast.show_toast("解锁被拒——钥石不足或已解锁", "warn") toast.show_toast("解锁被拒——钥石不足或已解锁", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
@@ -162,7 +195,7 @@ func on_camp_forge_select(route_id: String) -> void:
if not ok: if not ok:
toast.show_toast("切换被拒——路线未解锁", "warn") toast.show_toast("切换被拒——路线未解锁", "warn")
return return
camp_overlay.refresh_camp(camp_data()) views.refresh_bus.refresh(camp_data())
_refresh_presentations.call() _refresh_presentations.call()
+121 -268
View File
@@ -1,7 +1,8 @@
## VERTICAL SLICE - NOT FOR PRODUCTION ## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根:唯一的模式流转与 core↔view 接线处(垂直切片验证:线性第一关承载元进度循环)。 ## 组合根:唯一的模式流转与 core↔view 接线处(垂直切片验证:线性第一关承载元进度循环)。
## db.hero 运行期只读,加成经 runner.set_hero_override / debug_session 生效值入战斗。 ## db.hero 运行期只读,加成经 runner.set_hero_override / debug_session 生效值入战斗。
## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 _open_modal/_close_modal。 ## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.modals.flow(注册面编排口)。
## 界面句柄与层清单在 ui 注册面(ui_registry.gd)——屏壳/战区/模态三域句柄与 LAYER_CONTRACT 单点宿主。
## 局态全在 run_scope.gd 节点,禁在屏/视图侧持有局态引用;autoload 节点路径常量单点在 AUTOLOAD_PATH_*。 ## 局态全在 run_scope.gd 节点,禁在屏/视图侧持有局态引用;autoload 节点路径常量单点在 AUTOLOAD_PATH_*。
extends Control extends Control
@@ -15,10 +16,8 @@ const AUTOLOAD_PATH_META := "/root/MetaProgression"
const UiAssembler := preload("res://presentation/ui_assembler.gd") const UiAssembler := preload("res://presentation/ui_assembler.gd")
const Narrator := preload("res://presentation/battle_narrator.gd") const Narrator := preload("res://presentation/battle_narrator.gd")
const ModalStack := preload("res://presentation/modal_stack.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd") const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd") const DebugSessionScript := preload("res://presentation/debug_session.gd")
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互 const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互 const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影 const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影
@@ -31,33 +30,6 @@ const MenuScreenScene := preload("res://presentation/scenes/menu_screen.tscn")
const CampScreenScene := preload("res://presentation/scenes/camp_screen.tscn") const CampScreenScene := preload("res://presentation/scenes/camp_screen.tscn")
const AdventureScreenScene := preload("res://presentation/scenes/adventure_screen.tscn") const AdventureScreenScene := preload("res://presentation/scenes/adventure_screen.tscn")
# ==================== 视觉层合同(唯一事实源——树序=登记序=z 序;改序只改此表) ====================
# kind: shell=屏壳容器(成员随壳不占独立树位;level_screen 例外另占登记位)/modal=模态层(入登记册,
# tags 随登记)/state=全屏态(占树位不入登记册)/node=非视觉节点(占树位不入登记册)。
# host=宿住屏壳(树序随宿主、登记序按本合同);shell=模态成员的屏壳(成员与屏壳同槽:屏壳占该树位、
# 成员占该登记位)。落位=_apply_layer_contract(全仓唯一层序落位点,add_child 之后统一排序+登记)。
const LAYER_CONTRACT: Array[Dictionary] = [
{"key": "adventure_screen", "kind": "shell"},
{"key": "menu_screen", "kind": "shell"},
{"key": "row_pacer", "kind": "node"},
{"key": "legend_overlay", "kind": "modal", "tags": []},
{"key": "node_overlay", "kind": "modal", "tags": []},
{"key": "level_screen", "kind": "modal", "tags": [ModalStack.TAG_SCREEN_STATE], "host": "adventure_screen"},
{"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": "settings_overlay", "kind": "modal", "tags": []},
{"key": "status_panel", "kind": "modal", "tags": []},
{"key": "career_overlay", "kind": "modal", "tags": []},
{"key": "save_select_overlay", "kind": "modal", "tags": []}, # 选档界面(营地交互重做卡③——登记生涯同档)
{"key": "inter_overlay", "kind": "state", "tags": []},
{"key": "confirm_dialog", "kind": "modal", "tags": []},
{"key": "debug_overlay", "kind": "modal", "tags": [ModalStack.TAG_NON_LOCKING]},
{"key": "debug_badge", "kind": "float"}, # 调试入口全局浮层——末位=最顶绘制序;
# float 类不注册 ModalStack(open() 遮挡记账只隐藏登记层)→ 任何 UI 打开都不遮它
]
var db var db
var run_scope = null # 会话状态承载(run_scope.gd;_ready 即建闲时态,永非 null) var run_scope = null # 会话状态承载(run_scope.gd;_ready 即建闲时态,永非 null)
# SaveManager/MetaProgression 升格 autoload——成员名保留为「autoload 读视图」 # SaveManager/MetaProgression 升格 autoload——成员名保留为「autoload 读视图」
@@ -71,99 +43,24 @@ var meta: MetaProgressionCore:
return get_node(AUTOLOAD_PATH_META) return get_node(AUTOLOAD_PATH_META)
var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件)
var _debug_entry := false # 调试会话门控(缺省 false=发布态:角标恒隐、浮窗无 UI 开启口;沿革查 git log) var _debug_entry := false # 调试会话门控(缺省 false=发布态:角标恒隐、浮窗无 UI 开启口;沿革查 git log)
var _seed_edit: LineEdit var ui: UiRegistry # 界面注册面(ui_registry.gd——界面句柄/层序合同/模态编排句柄的单点宿主)
var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门)
var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清) var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清)
var _confirm_consumer_slot := 0 # 消费方附随槽号(delete_slot 消费方专用) var _confirm_consumer_slot := 0 # 消费方附随槽号(delete_slot 消费方专用)
var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(level-clear.md v2 选择面承载;奖励节点共用)
var _start_overlay: Control
var _end_overlay: BattleEndOverlay
var _inter_overlay: Control
var _inter_label: Label
var level_screen: LevelMapScreen # 关卡画面句柄(指向 adventure_screen.level_screen)
var _battle_root: Control # 战斗屏宿主句柄(指向 adventure_screen.battle_root)
# 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI) # 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI)
var _debug = null var _debug = null
# 视图(views/)——battle.md 三区(节点链进度
# 由关卡画面本体选路覆盖层承载)
var stage: BattleStageView # B 区(含 log_view 明细带)——句柄指向 adventure_screen
var log_view: BattleLogView
var info_panel: HeroInfoPanelView # C 区(攻击/AC 数据框+两预留区)
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
# 三屏壳实例(屏成员经本类句柄暴露;探针挂载假设 main.X 一级路径)
var _menu_screen = null # MenuScreen(开始菜单屏壳)
var _camp_screen = null # CampScreen(营地屏壳)
var _adventure_screen = null # AdventureScreen(冒险屏壳——内含关卡地图/战斗两容器)
# 拆件后的组合根持有面(职责见各类头注;装配见 _build_ui) # 拆件后的组合根持有面(职责见各类头注;装配见 _build_ui)
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图) var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入) var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入)
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发) var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
# 排播队列只读委托面(探针/测试读 main._row_queue——与 RowPacer 队列共享同一 Array 引用)
var _row_queue: Array[Variant]:
get:
return _row_pacer.pending_rows()
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
var legend_overlay: LegendOverlay
var node_overlay: NodeOverlay
var shop_overlay: ShopOverlay # 商店覆盖层(商店房节点兑付呈现)
var camp_overlay: CampOverlay # 营地覆盖层句柄(指向 camp_screen.camp_overlay)
var settings_store # 系统级设置独立存储(音量/全屏/减动效) var settings_store # 系统级设置独立存储(音量/全屏/减动效)
var settings_overlay: SettingsOverlay # 设置覆盖层(最小设置面)
var status_panel: StatusPanel # 局内状态面板·只读(钥石余额+营地三柜台进度查看;登记设置层同档)
var career_overlay: CareerOverlay # 生涯页·只读全屏页(跨局汇总六类九项;登记营地同档〔设置层旁、确认弹窗之前〕)
var save_select_overlay: SaveSelectOverlay # 选档界面·3 存档位(新档/进档/删档——营地交互重做卡③;登记生涯同档)
var debug_overlay: DebugOverlay # 调试浮窗(种子/数值调整/批量模拟/两层调试入口收一)
var debug_badge: Button # 调试浮层入口角标(UI 最顶层浮层——LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡)
var _modal_flow = null # 模态编排纯逻辑类(modal_flow.gd——依赖仅 ModalStack+查询 Callable)
var _modals = null # 模态层级状态机(=_modal_flow.stack——探针 _list 登记序断言缝保留)
var _tune_sim = null # 调试浮窗批量模拟控制器(tune_sim_controller.gd) var _tune_sim = null # 调试浮窗批量模拟控制器(tune_sim_controller.gd)
# 结算框挂起态属性化(探针/冒烟直读写接缝语义零变化——读=armed,写=arm/disarm;计时器随会话住
# run_scope.settlement_stop_timer)
var _settlement_stop_pending: bool:
get:
return run_scope.settlement_stop_timer.is_armed() if run_scope != null else false
set(v):
if run_scope == null:
return
if v:
run_scope.settlement_stop_timer.arm()
else:
run_scope.settlement_stop_timer.disarm()
# 锁期点击重放(v3.1):延迟一帧重入唯一动作通道——此刻队列空锁已解,按普通动作受理
#(局面在演播期无变化:逻辑先行、演播只是回放——点击语义保真;_row_queue=RowPacer 委托面,
# 排播泵本体在 row_pacer.gd)。
var _deferred_modal: Control: # 演播后开窗挂起只读视图(t17_defer_probe 挂起态断言缝;本体归 _modal_flow)
get:
return _modal_flow._deferred if _modal_flow != null else null
# 演播后排窗(伤害过程演完才切结算窗口):排播队列在播=挂起,演播完边沿
# (_on_pump_ended 派发)再开——旧路径 _open_modal 前置 flush 会把击杀轮的伤害/死亡演出
# 同帧倒掉(「直接跳战斗胜利」缺陷本体)。队列空=立即开(行为同旧路径)。
func _open_modal_after_playback(m: Control) -> void:
_modal_flow.open_after_playback(m) # 演播后排窗挂起编排归 ModalFlow
# 演播完边沿派发(row_pacer.pump_ended 注入口——先开挂起 modal,后锁期点击重放):
func _on_pump_ended() -> void:
_modal_flow.pump_ended() # 派发编排归 ModalFlow(重放经注入 Callable 回本类)
func _replay_locked_click() -> void: func _replay_locked_click() -> void:
if run_scope.locked_click.is_empty() or not _row_queue.is_empty(): if run_scope.locked_click.is_empty() or not ui.screens.row_pacer.pending_rows().is_empty():
return return
var click: Dictionary[Variant, Variant] = run_scope.locked_click var click: Dictionary[Variant, Variant] = run_scope.locked_click
run_scope.locked_click = {} run_scope.locked_click = {}
@@ -173,7 +70,7 @@ func _replay_locked_click() -> void:
# 会话生命周期:_ready 建闲时态(永非 null——全组合根零空判责), # 会话生命周期:_ready 建闲时态(永非 null——全组合根零空判责),
# 开局/续玩/调试两层各建新会话,旧会话整节点 queue_free()(帧末回收;新节点先挂树无真空窗)。 # 开局/续玩/调试两层各建新会话,旧会话整节点 queue_free()(帧末回收;新节点先挂树无真空窗)。
func _begin_session(adventure_session: bool) -> void: func _begin_session(adventure_session: bool) -> void:
_row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定) ui.screens.row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定)
if run_scope != null: if run_scope != null:
run_scope.queue_free() run_scope.queue_free()
run_scope = RunScope.new() run_scope = RunScope.new()
@@ -194,10 +91,11 @@ func _ready() -> void:
settings_store = SettingsStore.new() settings_store = SettingsStore.new()
settings_store.load_settings() settings_store.load_settings()
settings_store.apply() settings_store.apply()
ui = UiRegistry.new() # 注册面先于装配建(_build_ui 八步向容器登记——装配时序零变化)
_build_ui() _build_ui()
_begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) _begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序)
_start_overlay.visible = true ui.screens.start_overlay.visible = true
debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐 ui.screens.debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐
# boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住); # boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住);
# 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡) # 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡)
@@ -206,94 +104,13 @@ func _ready() -> void:
func _build_ui() -> void: func _build_ui() -> void:
MainUiBuild.build(self) MainUiBuild.build(self)
debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口 ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
func _fatal_screen(msg: String) -> void: func _fatal_screen(msg: String) -> void:
MainUiBuild.fatal_screen(self, msg) MainUiBuild.fatal_screen(self, msg)
# ==================== 模态 overlay 层级管理(pattern 6 / 缺陷⑤——状态机归 modal_stack.gd) ====================
func _register_modal(m: Control, tags: Array = []) -> void:
# 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后
# 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors
# ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律)
m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_modal_flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏)
# 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT)
func _layer_node(key: String) -> Node:
match key:
"adventure_screen": return _adventure_screen
"menu_screen": return _menu_screen
"row_pacer": return _row_pacer
"legend_overlay": return legend_overlay
"node_overlay": return node_overlay
"level_screen": return level_screen
"curio_overlay": return curio_overlay
"end_overlay": return _end_overlay
"shop_overlay": return shop_overlay
"camp_screen": return _camp_screen
"camp_overlay": return camp_overlay
"settings_overlay": return settings_overlay
"status_panel": return status_panel
"career_overlay": return career_overlay
"save_select_overlay": return save_select_overlay
"inter_overlay": return _inter_overlay
"confirm_dialog": return confirm_dialog
"debug_overlay": return debug_overlay
"debug_badge": return debug_badge
return null
# 合同装配(_build_ui 尾调用):按 LAYER_CONTRACT 序统一落位——直接子层 move_child 到合同槽、
# 模态层按合同序登记;宿住层(host)树序随宿主只登记;成员层(shell)与屏壳同槽(屏壳占树位、成员占登记位)。
# 本函数是全仓唯一层序落位点(move_child 单口)——改层序只改 LAYER_CONTRACT。
func _apply_layer_contract() -> void:
var slot := 0
for entry: Dictionary in LAYER_CONTRACT:
var tags: Array[Variant] = entry.get("tags", [])
if entry.has("shell"):
var shell: Node = _layer_node(String(entry["shell"]))
var member: Node = _layer_node(String(entry["key"]))
if shell != null:
move_child(shell, slot)
slot += 1
if member != null:
_register_modal(member as Control, tags)
else:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
var node: Node = _layer_node(String(entry["key"]))
if node == null:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
if entry.has("host"):
_register_modal(node as Control, tags)
continue
move_child(node, slot)
slot += 1
if String(entry["kind"]) == "modal":
_register_modal(node as Control, tags)
func _open_modal(m: Control) -> void:
_modal_flow.open(m) # 挂起兜底/排播排空/遮挡记账/开侧同步编排归 ModalFlow
func _close_modal(m: Control) -> void:
_modal_flow.close(m) # 关闭恢复+关侧同步归 ModalFlow(同上)
func _on_modal_closed(m: Control) -> void:
_close_modal(m)
func _input_locked() -> bool:
# 模态/非战斗期棋盘输入锁(判据编排归 ModalFlow.locks_input:互斥域随层登记
# [non_locking=调试浮窗]+ 三例外经查询 Callable 注入;本函数留探针缝)
return _modal_flow.locks_input()
# ==================== 冒险模式入口(唯一试玩入口)==================== # ==================== 冒险模式入口(唯一试玩入口)====================
func _start_adventure_from_menu() -> void: func _start_adventure_from_menu() -> void:
@@ -301,24 +118,42 @@ func _start_adventure_from_menu() -> void:
# 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针 # 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针
# 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝 # 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝
# 由卡① sv3 承接(结算不落盘)。 # 由卡① sv3 承接(结算不落盘)。
_start_overlay.visible = false ui.screens.start_overlay.visible = false
_start_adventure() _start_adventure()
# 确认弹窗分派(卡③ 契约 3):按 _confirm_consumer 路由——"delete_slot"=卡③ 删档确认 # 确认弹窗分派(卡③ 契约 3):按 _confirm_consumer 路由——"delete_slot"=卡③ 删档确认
#(确认才删、取消只关窗——裁决 3)。"new_adventure" 消费方已随卡② 主菜单确认链摘除退役 #(确认才删、取消只关窗——裁决 3)。"gate"/"gate_new"=卡④ 大门两分支消费方(有/无断点),
#(mn6/V5——主菜单零确认残留),分派机制保留(卡④ 到位接入第三消费方不返工)。 # "campfire_leave"=篝火二次确认(卡④ T4 接入——分派机制扩展第三/第四消费方)。
func _on_confirm_accepted() -> void: func _on_confirm_accepted() -> void:
_close_modal(confirm_dialog) # 模态栈单口 ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
match _confirm_consumer: match _confirm_consumer:
"delete_slot": "delete_slot":
save_manager.slots.delete_slot(_confirm_consumer_slot) save_manager.slots.delete_slot(_confirm_consumer_slot)
save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态) 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 = "" _confirm_consumer = ""
func _on_confirm_declined() -> void: func _on_confirm_declined() -> void:
_close_modal(confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删);关侧恢复归 ModalFlow ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow
_confirm_consumer = "" _confirm_consumer = ""
@@ -334,16 +169,16 @@ func _resume_from_menu() -> void:
if not save_manager.has_breakpoint(): if not save_manager.has_breakpoint():
return return
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot() var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
_close_modal(debug_overlay) # 开冒险路径显式关浮窗(续玩同口径) ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
_start_overlay.visible = false ui.screens.start_overlay.visible = false
_begin_session(true) _begin_session(true)
_wire_adventure() _wire_adventure()
run_scope.level_active = true run_scope.level_active = true
_sync_status_entry() # 续玩恢复点亮状态面板入口角标 _sync_status_entry() # 续玩恢复点亮状态面板入口角标
level_screen.set_abandon_available(true) ui.modals.level_screen.set_abandon_available(true)
log_view.clear() ui.battle.log_view.clear()
logger.start_session(int(bp.get("master_seed", 0))) logger.start_session(int(bp.get("master_seed", 0)))
run_scope.adventure.set_hero_override(debug_overlay.atk_pct(), debug_overlay.hp_pct()) run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct())
# 同调三键随续玩下传(build_adventure_params 统一构造,与开新局同源同口径) # 同调三键随续玩下传(build_adventure_params 统一构造,与开新局同源同口径)
if not run_scope.adventure.resume_adventure(bp, build_adventure_params(save_manager, meta, if not run_scope.adventure.resume_adventure(bp, build_adventure_params(save_manager, meta,
{"treasure_unlocked": _treasure_ui.unlocked_set()})): {"treasure_unlocked": _treasure_ui.unlocked_set()})):
@@ -379,16 +214,16 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接; # 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根) # 信号连接只在组合根)
func _start_adventure() -> void: func _start_adventure() -> void:
_close_modal(debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开) ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
_begin_session(true) _begin_session(true)
_wire_adventure() _wire_adventure()
run_scope.level_active = true run_scope.level_active = true
_sync_status_entry() # 进对局点亮状态面板入口角标 _sync_status_entry() # 进对局点亮状态面板入口角标
level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期) ui.modals.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期)
log_view.clear() ui.battle.log_view.clear()
var seed_val := _seed_value() var seed_val := _seed_value()
logger.start_session(seed_val) logger.start_session(seed_val)
run_scope.adventure.set_hero_override(debug_overlay.atk_pct(), debug_overlay.hp_pct()) run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct())
# 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板) # 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42) # 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42)
if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta, if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta,
@@ -435,33 +270,33 @@ func _on_inter_next_pressed() -> void:
func _show_board_fx_for_node(node_type: String = "") -> void: func _show_board_fx_for_node(node_type: String = "") -> void:
# Boss 遭遇变体:紫赤影子爬棋盘一次性占位演出(§2.6) # Boss 遭遇变体:紫赤影子爬棋盘一次性占位演出(§2.6)
if node_type == "boss": if node_type == "boss":
board_zone.fx.play_boss_shadow() ui.battle.board_zone.fx.play_boss_shadow()
func _on_battle_finished(payload: Dictionary) -> void: func _on_battle_finished(payload: Dictionary) -> void:
# 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派) # 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派)
var result: String = String(payload.get("result", "")) var result: String = String(payload.get("result", ""))
_sync_settlement_visuals() # → 全关(battle_over 反馈面) _sync_settlement_visuals() # → 全关(battle_over 反馈面)
HudPresenter.battle_end_feedback(stage, ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道 HudPresenter.battle_end_feedback(ui.battle.stage, ui.battle.board_zone.ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道
var decision: Dictionary[Variant, Variant] = _debug.on_battle_finished(payload) var decision: Dictionary[Variant, Variant] = _debug.on_battle_finished(payload)
match String(decision.outcome): match String(decision.outcome):
"next_layer": "next_layer":
_inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [ ui.screens.inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [
int(decision.layer), int(decision.carry_hp), int(db.combat.hero.max_hp)] int(decision.layer), int(decision.carry_hp), int(db.combat.hero.max_hp)]
_inter_overlay.visible = true ui.screens.inter_overlay.visible = true
_refresh_presentations() _refresh_presentations()
return return
"level_end": "level_end":
_end_overlay.open_victory(Narrator.battle_end_text("victory", true), ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", true),
"返回", board_view.get_global_rect(), _end_victory_opts(payload)) "返回", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload))
"standalone_end": "standalone_end":
if result == "victory": if result == "victory":
_end_overlay.open_victory(Narrator.battle_end_text("victory", false), ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", false),
"回到遭遇选择", board_view.get_global_rect(), _end_victory_opts(payload)) "回到遭遇选择", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload))
else: else:
_end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), ui.modals.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false),
"返回", board_view.get_global_rect()) "返回", ui.battle.board_zone.board_view.get_global_rect())
_open_modal_after_playback(_end_overlay) ui.modals.flow.open_after_playback(ui.modals.end_overlay)
_refresh_presentations() _refresh_presentations()
@@ -499,22 +334,17 @@ func _on_step_row(row: Dictionary) -> void:
# 清图奖励可感知反馈(挖完全图须有奖励感——clear_blast 兑付对敌伤害 # 清图奖励可感知反馈(挖完全图须有奖励感——clear_blast 兑付对敌伤害
# 零可见,toast 桥接归组合根呈现层;零兑付(未生效)不弹安静留明细带) # 零可见,toast 桥接归组合根呈现层;零兑付(未生效)不弹安静留明细带)
if String(row.get("source_class", "")) == "clear_reward" and String(row.get("type", "")) != "reward_ineffective": if String(row.get("source_class", "")) == "clear_reward" and String(row.get("type", "")) != "reward_ineffective":
board_zone.toast.show_toast("清图奖励:%s" % String(row.get("display", "")).replace("(清图奖励)", ""), "info") ui.battle.board_zone.toast.show_toast("清图奖励:%s" % String(row.get("display", "")).replace("(清图奖励)", ""), "info")
# 排播入口:减动效=延迟归零同帧直落(行呈现不跳过);常态入队按段阶梯播放 # 排播入口:减动效=延迟归零同帧直落(行呈现不跳过);常态入队按段阶梯播放
if UiKit.motion_scale < 1.0: if UiKit.motion_scale < 1.0:
_row_pacer.consume_now(row) ui.screens.row_pacer.consume_now(row)
return return
_row_pacer.enqueue(row) ui.screens.row_pacer.enqueue(row)
# 段间延迟取数面(减动效双态断言用——pacing_queue_test 直读;委托 row_pacer 同名面) # 段间延迟取数面(减动效双态断言用——pacing_queue_test 直读;委托 row_pacer 同名面)
func pace_delay_for(prev_seg: String, next_seg: String) -> float: func pace_delay_for(prev_seg: String, next_seg: String) -> float:
return _row_pacer.pace_delay_for(prev_seg, next_seg) return ui.screens.row_pacer.pace_delay_for(prev_seg, next_seg)
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整;pacing_queue_test 直读)
func _flush_row_queue() -> void:
_row_pacer.flush()
func _on_round_started(ri: int) -> void: func _on_round_started(ri: int) -> void:
@@ -522,10 +352,10 @@ func _on_round_started(ri: int) -> void:
#(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过——consume_now 复用 #(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过——consume_now 复用
# _consume_row 轮首分隔行分支)。 # _consume_row 轮首分隔行分支)。
if UiKit.motion_scale < 1.0: if UiKit.motion_scale < 1.0:
_row_pacer.consume_now({"__round_line": ri}) ui.screens.row_pacer.consume_now({"__round_line": ri})
else: else:
_row_pacer.enqueue({"__round_line": ri}) ui.screens.row_pacer.enqueue({"__round_line": ri})
settlement_fx.play_round_banner(ri) ui.battle.settlement_fx.play_round_banner(ri)
# 秘宝呈现同步:每战首轮装配链 # 秘宝呈现同步:每战首轮装配链
# 显式重算〔🔴 契约复原语义:下一战装配链重算即含「未装配→写 0」复原〕。 # 显式重算〔🔴 契约复原语义:下一战装配链重算即含「未装配→写 0」复原〕。
# 挂点时序依据:round_started 首轮发射位在 begin_battle 尾(attunement 注入之后)。 # 挂点时序依据:round_started 首轮发射位在 begin_battle 尾(attunement 注入之后)。
@@ -534,7 +364,7 @@ func _on_round_started(ri: int) -> void:
_sync_treasure_glow() _sync_treasure_glow()
# 巢穴预告呈现(装板报数量读数口消费:≥0=本战装配命中,toast 一次性提示) # 巢穴预告呈现(装板报数量读数口消费:≥0=本战装配命中,toast 一次性提示)
if run_scope.settlement != null and int(run_scope.settlement.nest_preview_count) >= 0: if run_scope.settlement != null and int(run_scope.settlement.nest_preview_count) >= 0:
board_zone.toast.show_toast("巢穴预告——本板危险格共 %d 个" % int(run_scope.settlement.nest_preview_count), "info") ui.battle.board_zone.toast.show_toast("巢穴预告——本板危险格共 %d 个" % int(run_scope.settlement.nest_preview_count), "info")
# 开战横幅——整图地形变异预告:首轮快照 board_variants # 开战横幅——整图地形变异预告:首轮快照 board_variants
# 非空才发射(Boss 图恒带一型=替换制;普通图空快照零发射);轮横幅下方叠行,view 内判减动效。 # 非空才发射(Boss 图恒带一型=替换制;普通图空快照零发射);轮横幅下方叠行,view 内判减动效。
if ri == 1 and run_scope.settlement != null: if ri == 1 and run_scope.settlement != null:
@@ -542,7 +372,7 @@ func _on_round_started(ri: int) -> void:
if not bvs.is_empty(): if not bvs.is_empty():
var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs) var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs)
if not vlines.is_empty(): if not vlines.is_empty():
settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines)) ui.battle.settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines))
_refresh_presentations() _refresh_presentations()
@@ -552,19 +382,19 @@ func _on_board_changed() -> void:
run_scope.settlement = run_scope.runner.current_battle() run_scope.settlement = run_scope.runner.current_battle()
if run_scope.settlement == null: if run_scope.settlement == null:
return return
board_view.rebuild(run_scope.settlement.minesweeper.current_board(), run_scope.settlement.minesweeper) ui.battle.board_zone.board_view.rebuild(run_scope.settlement.minesweeper.current_board(), run_scope.settlement.minesweeper)
_check_board_hints() _check_board_hints()
_refresh_presentations() _refresh_presentations()
# 秘宝视野呈现桥:阔视野带 light_radius_bonus # 秘宝视野呈现桥:阔视野带 light_radius_bonus
# 装配参数合计显式写给 board_view.set_fog_glow_bonus(装配=半径 8+4 / 未装配=复原 0),写入即 setter # 装配参数合计显式写给 ui.battle.board_zone.board_view.set_fog_glow_bonus(装配=半径 8+4 / 未装配=复原 0),写入即 setter
# 内 refresh 全板重算雾态。调用点=每战首轮装配链(_on_round_started ri==1)——纯呈现边界 🔴 契约 2: # 内 refresh 全板重算雾态。调用点=每战首轮装配链(_on_round_started ri==1)——纯呈现边界 🔴 契约 2:
# 只写呈现态参数,minesweeper 结算零触碰;off 态(未装配)写 0=缺省基线。 # 只写呈现态参数,minesweeper 结算零触碰;off 态(未装配)写 0=缺省基线。
func _sync_treasure_glow() -> void: func _sync_treasure_glow() -> void:
if run_scope.settlement == null or board_view == null: if run_scope.settlement == null or ui.battle.board_zone.board_view == null:
return return
board_view.set_fog_glow_bonus(run_scope.settlement.attune_param_sum("light_radius_bonus")) ui.battle.board_zone.board_view.set_fog_glow_bonus(run_scope.settlement.attune_param_sum("light_radius_bonus"))
# ---- 一次性非模态提示(每 run 一次——pattern 10)与诚实提示 ---- # ---- 一次性非模态提示(每 run 一次——pattern 10)与诚实提示 ----
@@ -575,10 +405,10 @@ func _check_board_hints() -> void:
return return
# 零合法板(棋盘生成 EC5 降级)诚实提示——非静默(toast,非模态) # 零合法板(棋盘生成 EC5 降级)诚实提示——非静默(toast,非模态)
if board.illegal_product: if board.illegal_product:
board_zone.toast.show_toast("本板为零合法板降级产物——出图器已尽力,数据面诚实告知", "warn") ui.battle.board_zone.toast.show_toast("本板为零合法板降级产物——出图器已尽力,数据面诚实告知", "warn")
# 接受最优降级(有合法板但无一达标)——降级标记独立于非法产物标记,同走诚实告知 # 接受最优降级(有合法板但无一达标)——降级标记独立于非法产物标记,同走诚实告知
elif board.degraded_product: elif board.degraded_product:
board_zone.toast.show_toast("本板为接受最优降级产物(未达可推理率及格线)——数据面诚实告知", "warn") ui.battle.board_zone.toast.show_toast("本板为接受最优降级产物(未达可推理率及格线)——数据面诚实告知", "warn")
func _on_hover_forbidden(_cell: Vector2i) -> void: func _on_hover_forbidden(_cell: Vector2i) -> void:
@@ -594,7 +424,7 @@ func _on_chord_attempted(_cell: Vector2i) -> void:
func _show_hint(text: String, hint_id: String) -> void: func _show_hint(text: String, hint_id: String) -> void:
run_scope.hints_shown[hint_id] = true run_scope.hints_shown[hint_id] = true
logger.log_ux("ux_hint_shown", {"hint_id": hint_id}) logger.log_ux("ux_hint_shown", {"hint_id": hint_id})
board_zone.toast.show_toast(text, "info") ui.battle.board_zone.toast.show_toast(text, "info")
# ---- 节点覆盖层"继续"(奖励/恢复点兑付确认)与节点图选路覆盖层 ---- # ---- 节点覆盖层"继续"(奖励/恢复点兑付确认)与节点图选路覆盖层 ----
@@ -688,12 +518,35 @@ func _sync_status_entry() -> void:
MainOverlayFlow._sync_status_entry(self) 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: func _on_camp_closed() -> void:
MainOverlayFlow._on_camp_closed(self) 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: func _camp_data() -> Dictionary:
return MainOverlayFlow._camp_data(self) return MainOverlayFlow._camp_data(self)
@@ -706,15 +559,15 @@ func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void:
func _on_board_action(cell: Vector2i, kind: String) -> void: func _on_board_action(cell: Vector2i, kind: String) -> void:
# 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口) # 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口)
if _input_locked(): if ui.modals.flow.locks_input():
return return
# 结算演播期棋盘冻结(v3 口径——不可点/不可移/不可挖): # 结算演播期棋盘冻结(v3 口径——不可点/不可移/不可挖):
# 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。 # 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。
# 队列排空即解锁、无状态残留;HUD 按钮不在锁面(停手/明细回看各自通路,开启前 # 队列排空即解锁、无状态残留;HUD 按钮不在锁面(停手/明细回看各自通路,开启前
# _flush_row_queue 自行排空)。 # 开层前排空钩子(flow.flush_rows——模态开启前明细带恒完整)。
# **点击不丢弃**(v3.1——「卡死」体感根因):锁期点击缓存最后一次,演播完边沿 # **点击不丢弃**(v3.1——「卡死」体感根因):锁期点击缓存最后一次,演播完边沿
# (_process 泵 _was_pumping 分支)自动重放——玩家操作零丢失、体感=「演播一停动作就生效」。 # (_process 泵 _was_pumping 分支)自动重放——玩家操作零丢失、体感=「演播一停动作就生效」。
if not _row_queue.is_empty(): if not ui.screens.row_pacer.pending_rows().is_empty():
run_scope.locked_click = {"cell": cell, "kind": kind} run_scope.locked_click = {"cell": cell, "kind": kind}
return return
# 秘宝选中态劫持(两步交互第二步):点格=使用目标拾取,不落挖格; # 秘宝选中态劫持(两步交互第二步):点格=使用目标拾取,不落挖格;
@@ -734,22 +587,22 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
# settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被 # settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被
# runner 置空,清图演出判定须先判 settlement(重入隐患守卫) # runner 置空,清图演出判定须先判 settlement(重入隐患守卫)
if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper.current_board() != null and run_scope.settlement.minesweeper.clear_map_cleared(): if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper.current_board() != null and run_scope.settlement.minesweeper.clear_map_cleared():
board_zone.fx.play_clear_burst() ui.battle.board_zone.fx.play_clear_burst()
if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper != null: if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper != null:
# 挖掘演出:挥铲三帧 + 角色弹跳——触发=格子点开那一刻 # 挖掘演出:挥铲三帧 + 角色弹跳——触发=格子点开那一刻
#(settle_batch 同步受理即状态已变,动画纯表现与 refresh 同拍零竞态; #(settle_batch 同步受理即状态已变,动画纯表现与 refresh 同拍零竞态;
# 挖掘不移动英雄,出手方向 = 目标格−英雄格,圆心=英雄持握手位) # 挖掘不移动英雄,出手方向 = 目标格−英雄格,圆心=英雄持握手位)
var hero_cell: Vector2i = run_scope.settlement.minesweeper.hero_pos() var hero_cell: Vector2i = run_scope.settlement.minesweeper.hero_pos()
board_zone.fx.play_shovel_swing(board_view.hero_hand_point(), cell - hero_cell, board_view.hero_sprite_scale()) ui.battle.board_zone.fx.play_shovel_swing(ui.battle.board_zone.board_view.hero_hand_point(), cell - hero_cell, ui.battle.board_zone.board_view.hero_sprite_scale())
board_view.play_hero_dig_bounce() ui.battle.board_zone.board_view.play_hero_dig_bounce()
elif accepted and kind == "move": elif accepted and kind == "move":
board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知 ui.battle.board_zone.board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知
board_view.refresh() ui.battle.board_zone.board_view.refresh()
_refresh_presentations() _refresh_presentations()
func _on_stop_pressed() -> void: func _on_stop_pressed() -> void:
if _input_locked(): if ui.modals.flow.locks_input():
return return
# stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济) # stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济)
if run_scope.settlement == null: if run_scope.settlement == null:
@@ -758,15 +611,15 @@ func _on_stop_pressed() -> void:
logger.log_action("stop", Vector2i(-1, -1), not outcomes.is_empty() and bool(outcomes[0].accepted)) logger.log_action("stop", Vector2i(-1, -1), not outcomes.is_empty() and bool(outcomes[0].accepted))
if not outcomes.is_empty() and String(outcomes[0].get("outcome", "")) == "voided": if not outcomes.is_empty() and String(outcomes[0].get("outcome", "")) == "voided":
# voided ≠ 拒绝——独立文案(pattern 1:防静默改判读感欺骗) # voided ≠ 拒绝——独立文案(pattern 1:防静默改判读感欺骗)
board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn") ui.battle.board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn")
logger.log_ux("ux_reject", {"source": "voided", "sub_reason": "stop_voided"}) logger.log_ux("ux_reject", {"source": "voided", "sub_reason": "stop_voided"})
board_view.refresh() ui.battle.board_zone.board_view.refresh()
_refresh_presentations() _refresh_presentations()
func _on_refresh_confirmed() -> void: func _on_refresh_confirmed() -> void:
# 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板封印作废,每轮上限读表) # 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板封印作废,每轮上限读表)
if _input_locked(): if ui.modals.flow.locks_input():
return return
var ok: bool = false var ok: bool = false
if run_scope.level_active and run_scope.runner != null: if run_scope.level_active and run_scope.runner != null:
@@ -776,7 +629,7 @@ func _on_refresh_confirmed() -> void:
logger.log_action("refresh", Vector2i(-1, -1), ok) logger.log_action("refresh", Vector2i(-1, -1), ok)
if ok: if ok:
logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": _refresh_used_snapshot()}) logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": _refresh_used_snapshot()})
board_zone.toast.show_toast("RAP−%d · 旧板封印作废——新图已就位,AP 保留继续挖" % int(db.run.action_economy.rap_refresh_cost), "gold") ui.battle.board_zone.toast.show_toast("RAP−%d · 旧板封印作废——新图已就位,AP 保留继续挖" % int(db.run.action_economy.rap_refresh_cost), "gold")
else: else:
# 资源面/上限拒绝(EC13 合法拒绝可见;行动经济 无载荷——客户端按上下文推断) # 资源面/上限拒绝(EC13 合法拒绝可见;行动经济 无载荷——客户端按上下文推断)
var rap: int = int(run_scope.settlement.presentation_snapshot().get("rap", 0)) if run_scope.settlement != null else 0 var rap: int = int(run_scope.settlement.presentation_snapshot().get("rap", 0)) if run_scope.settlement != null else 0
@@ -784,11 +637,11 @@ func _on_refresh_confirmed() -> void:
var result := "rejected_rap" if rap < cost else "rejected_cap" var result := "rejected_rap" if rap < cost else "rejected_cap"
logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_snapshot() + 1}) logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_snapshot() + 1})
if result == "rejected_rap": if result == "rejected_rap":
board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn") ui.battle.board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn")
ap_bar.pulse_rap() ui.battle.board_zone.ap_bar.pulse_rap()
else: else:
board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn") ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn")
board_view.refresh() ui.battle.board_zone.board_view.refresh()
_refresh_presentations() _refresh_presentations()
@@ -798,9 +651,9 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void:
return return
logger.log_ux("ux_reject", {"source": String(outcome.get("reject_reason", "")), logger.log_ux("ux_reject", {"source": String(outcome.get("reject_reason", "")),
"sub_reason": String(outcome.get("sub_reason", ""))}) "sub_reason": String(outcome.get("sub_reason", ""))})
board_zone.toast.show_toast(HudPresenter.outcome_text(outcome), "warn") ui.battle.board_zone.toast.show_toast(HudPresenter.outcome_text(outcome), "warn")
if String(outcome.get("reject_reason", "")) == "resource_face": if String(outcome.get("reject_reason", "")) == "resource_face":
ap_bar.pulse_ap() ui.battle.board_zone.ap_bar.pulse_ap()
# ==================== 全屏 States 状态机(挖掘/结算/窗口/终局)==================== # ==================== 全屏 States 状态机(挖掘/结算/窗口/终局)====================
@@ -816,18 +669,18 @@ func _refresh_used_snapshot() -> int:
func _refresh_presentations() -> void: func _refresh_presentations() -> void:
# 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查) # 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查)
var refresh_remaining: int = maxi(0, int(db.run.action_economy.refresh_cap_per_round) - _refresh_used_snapshot()) var refresh_remaining: int = maxi(0, int(db.run.action_economy.refresh_cap_per_round) - _refresh_used_snapshot())
HudPresenter.refresh(stage, info_panel, ap_bar, {"settlement": run_scope.settlement, "runner": run_scope.runner, "level_active": run_scope.level_active, "db": db, "refresh_remaining": refresh_remaining}) HudPresenter.refresh(ui.battle.stage, ui.battle.info_panel, ui.battle.board_zone.ap_bar, {"settlement": run_scope.settlement, "runner": run_scope.runner, "level_active": run_scope.level_active, "db": db, "refresh_remaining": refresh_remaining})
_treasure_ui.refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗) _treasure_ui.refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗)
_sync_settlement_visuals() _sync_settlement_visuals()
func _sync_settlement_visuals() -> void: func _sync_settlement_visuals() -> void:
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开 84px # 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开(高度读 battle_layout)
var settling := _is_settling() var settling := _is_settling()
if settling and not run_scope.was_settling: if settling and not run_scope.was_settling:
settlement_fx.play_round_sweep(board_view.get_global_rect()) # 结算轮光带——时序敏感演出触发,例外留在组合根 ui.battle.settlement_fx.play_round_sweep(ui.battle.board_zone.board_view.get_global_rect()) # 结算轮光带——时序敏感演出触发,例外留在组合根
run_scope.was_settling = settling run_scope.was_settling = settling
HudPresenter.sync_settlement_visuals(stage, board_view, log_view, settling, HudPresenter.sync_settlement_visuals(ui.battle.stage, ui.battle.board_zone.board_view, ui.battle.log_view, settling,
HudPresenter.phase_text(run_scope.settlement, run_scope.runner, run_scope.boss_active, db)) HudPresenter.phase_text(run_scope.settlement, run_scope.runner, run_scope.boss_active, db))
@@ -836,13 +689,13 @@ func _is_settling() -> bool:
# 判据若只跟逻辑态,演播期棋盘会提前恢复可挖观感=「没意识到在结算演出」感知缺口根源; # 判据若只跟逻辑态,演播期棋盘会提前恢复可挖观感=「没意识到在结算演出」感知缺口根源;
# 演播期维持去饱和+禁挖光(结算中语义),放完自然回挖掘认知。判据=纯队列(排播泵尾迹 # 演播期维持去饱和+禁挖光(结算中语义),放完自然回挖掘认知。判据=纯队列(排播泵尾迹
# 窗口不可作演播中标志——row_pacer._process 注)。battle 收尾演播例外: # 窗口不可作演播中标志——row_pacer._process 注)。battle 收尾演播例外:
# overlay 开启即 _flush_row_queue 排空,视觉随队列空同步交接结算框。 # overlay 开启经 flow.flush_rows 钩子排空,视觉随队列空同步交接结算框。
# 相位取数经 presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透)。 # 相位取数经 presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透)。
if run_scope.settlement == null or not run_scope.settlement.is_battle_live(): if run_scope.settlement == null or not run_scope.settlement.is_battle_live():
return not _row_queue.is_empty() return not ui.screens.row_pacer.pending_rows().is_empty()
if String(run_scope.settlement.presentation_snapshot().get("phase", "")) == ActionEconomy.PHASE_WAITING: if String(run_scope.settlement.presentation_snapshot().get("phase", "")) == ActionEconomy.PHASE_WAITING:
return true return true
return not _row_queue.is_empty() return not ui.screens.row_pacer.pending_rows().is_empty()
# 相位行 —— 承接体见 HudPresenter.phase_text(caller 直接改引) # 相位行 —— 承接体见 HudPresenter.phase_text(caller 直接改引)
@@ -908,8 +761,8 @@ func _seed_value() -> int:
# 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机); # 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机);
# ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service # ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service
# 封闭流、禁 randomize()/自建 RNG——组合根建种子属引擎熵豁免面,不进玩法 RNG 消费) # 封闭流、禁 randomize()/自建 RNG——组合根建种子属引擎熵豁免面,不进玩法 RNG 消费)
if _seed_edit.text.is_valid_int(): if ui.modals.debug_overlay.seed_edit.text.is_valid_int():
return int(_seed_edit.text) return int(ui.modals.debug_overlay.seed_edit.text)
if save_manager.meta_state.career_state().first_adventure_done: if save_manager.meta_state.career_state().first_adventure_done:
return randi() # rng-seed-exempt(非首局空框引擎熵——组合根建种子不进玩法 RNG 消费,static_scan S1/S2 标记豁免) return randi() # rng-seed-exempt(非首局空框引擎熵——组合根建种子不进玩法 RNG 消费,static_scan S1/S2 标记豁免)
return 42 return 42
+45 -45
View File
@@ -30,10 +30,10 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display
# 开场行只显示 atk/max_hp 两值,注记指向不存在的显示轴反成失真),白板局不带 # 开场行只显示 atk/max_hp 两值,注记指向不存在的显示轴反成失真),白板局不带
var ov: Dictionary[Variant, Variant] = root.meta.hero_override() var ov: Dictionary[Variant, Variant] = root.meta.hero_override()
var boosted: bool = int(ov.get("atk", 0)) > 0 or int(ov.get("max_hp", 0)) > 0 \ var boosted: bool = int(ov.get("atk", 0)) > 0 or int(ov.get("max_hp", 0)) > 0 \
or root.debug_overlay.atk_pct() > 0 or root.debug_overlay.hp_pct() > 0 \ or root.ui.modals.debug_overlay.atk_pct() > 0 or root.ui.modals.debug_overlay.hp_pct() > 0 \
or root.run_scope.runner.journal().bonus_total("atk") > 0 \ or root.run_scope.runner.journal().bonus_total("atk") > 0 \
or root.run_scope.runner.journal().bonus_total("max_hp") > 0 or root.run_scope.runner.journal().bonus_total("max_hp") > 0
root.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed), root.ui.battle.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed),
root.run_scope.runner.effective_atk(), root.run_scope.runner.effective_max_hp(), boosted)) root.run_scope.runner.effective_atk(), root.run_scope.runner.effective_max_hp(), boosted))
# runner 六信号重连(is_connected 守卫——未连即接;守卫失败 push_error=重连恰好一次契约承载) # runner 六信号重连(is_connected 守卫——未连即接;守卫失败 push_error=重连恰好一次契约承载)
@@ -62,30 +62,30 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
root.logger.attach_battle(root.run_scope.settlement) root.logger.attach_battle(root.run_scope.settlement)
root.run_scope.boss_active = t == "boss" root.run_scope.boss_active = t == "boss"
root._show_board_fx_for_node(t) root._show_board_fx_for_node(t)
root.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t)) root.ui.battle.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t))
elif t == "reward": elif t == "reward":
# 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。 # 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
# 呈现编排归 curio_interact_controller.present_node_curio。 # 呈现编排归 curio_interact_controller.present_node_curio。
root._bonus.present_node_curio() root._bonus.present_node_curio()
root.log_view.append_bb(root.Narrator.reward_line()) root.ui.battle.log_view.append_bb(root.Narrator.reward_line())
elif t == "rest": elif t == "rest":
var healed: int = int(info.get("healed", 0)) var healed: int = int(info.get("healed", 0))
root._open_modal(root.node_overlay) root.ui.modals.flow.open(root.ui.modals.node_overlay)
root.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))) root.ui.modals.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
root.log_view.append_bb(root.Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))) root.ui.battle.log_view.append_bb(root.Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))))
elif t == "shop": elif t == "shop":
# 商店房兑付:到达即开店——商品/货架快照与余额经 runner 只读投影喂入; # 商店房兑付:到达即开店——商品/货架快照与余额经 runner 只读投影喂入;
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。 # 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。
# pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载) # pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载)
root._open_modal(root.shop_overlay) root.ui.modals.flow.open(root.ui.modals.shop_overlay)
if root.run_scope.runner.shop.is_shop_pool(): if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp)) root.ui.modals.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行 root.ui.modals.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1 root.ui.modals.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1
else: else:
root.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) root.ui.modals.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载) root.ui.modals.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp))) root.ui.battle.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
root.logger.log_node(node, info, root.run_scope.runner.node_index) root.logger.log_node(node, info, root.run_scope.runner.node_index)
root._refresh_presentations() root._refresh_presentations()
@@ -101,7 +101,7 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
_exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口 _exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
root._sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现) root._sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现)
root._sync_settlement_visuals() # → 全关 root._sync_settlement_visuals() # → 全关
HudPresenter.battle_end_feedback(root.stage, root.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道 HudPresenter.battle_end_feedback(root.ui.battle.stage, root.ui.battle.board_zone.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道
# 存档入账:settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变—— # 存档入账:settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。 # 调试两层不经过本处理器,天然不写档不污染生涯数据)。
var detail: Dictionary[String, Variant] = {} var detail: Dictionary[String, Variant] = {}
@@ -110,30 +110,30 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
if not detail.is_empty(): if not detail.is_empty():
text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup()) text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup())
if result == "completed": if result == "completed":
root._end_overlay.open_victory(text, "返回冒险", root.board_view.get_global_rect(), root.ui.modals.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(),
{"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账 {"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账
else: else:
root._end_overlay.open_defeat(text, "返回", root.board_view.get_global_rect()) root.ui.modals.end_overlay.open_defeat(text, "返回", root.ui.battle.board_zone.board_view.get_global_rect())
root.logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷) root.logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷)
if root.run_scope.runner != null: if root.run_scope.runner != null:
root.run_scope.runner.restore_hero_base() root.run_scope.runner.restore_hero_base()
root._open_modal_after_playback(root._end_overlay) root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root._refresh_presentations() root._refresh_presentations()
static func _on_battle_end_continue(root: Variant) -> void: static func _on_battle_end_continue(root: Variant) -> void:
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档) # 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档)
root._close_modal(root._end_overlay) root.ui.modals.flow.close(root.ui.modals.end_overlay)
root._end_overlay.close() root.ui.modals.end_overlay.close()
# 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面 # 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller) if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller)
return return
# 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现) # 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
if root._settlement_stop_pending: if root.run_scope.settlement_stop_timer.is_armed():
root._settlement_stop_pending = false root.run_scope.settlement_stop_timer.disarm()
_enter_level_screen(root) _enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner) root.ui.modals.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root.run_scope.pending_candidates = [] root.run_scope.pending_candidates = []
root._refresh_presentations() root._refresh_presentations()
return return
@@ -141,12 +141,12 @@ static func _on_battle_end_continue(root: Variant) -> void:
# 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd) # 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd)
root._debug.reset() root._debug.reset()
root._begin_session(false) root._begin_session(false)
root.debug_overlay.reset_sliders() root.ui.modals.debug_overlay.reset_sliders()
root._start_overlay.visible = true root.ui.screens.start_overlay.visible = true
root._refresh_presentations() root._refresh_presentations()
static func _on_inter_next_pressed(root: Variant) -> void: static func _on_inter_next_pressed(root: Variant) -> void:
root._inter_overlay.visible = false root.ui.screens.inter_overlay.visible = false
if root.run_scope.is_adventure: if root.run_scope.is_adventure:
# 冒险模式:大奖励屏确认 → 进下一关(AdventureRunner 序列推进——零自动推进的冒险级停点) # 冒险模式:大奖励屏确认 → 进下一关(AdventureRunner 序列推进——零自动推进的冒险级停点)
if root.run_scope.adventure != null and not root.run_scope.adventure.continue_to_next_level(): if root.run_scope.adventure != null and not root.run_scope.adventure.continue_to_next_level():
@@ -158,7 +158,7 @@ static func _on_inter_next_pressed(root: Variant) -> void:
static func _on_node_continue(root: Variant) -> void: static func _on_node_continue(root: Variant) -> void:
root._close_modal(root.node_overlay) root.ui.modals.flow.close(root.ui.modals.node_overlay)
if root.run_scope.runner != null: if root.run_scope.runner != null:
# 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口 # 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口
root.run_scope.runner.continue_after_node() root.run_scope.runner.continue_after_node()
@@ -167,33 +167,33 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void:
# 零自动推进的停点呈现:整幅节点图 + 图上点击选路(战斗胜利/兑付确认统一到达此处) # 零自动推进的停点呈现:整幅节点图 + 图上点击选路(战斗胜利/兑付确认统一到达此处)
if root.run_scope.runner == null: if root.run_scope.runner == null:
return return
root.log_view.append_bb(root.Narrator.route_line(candidates, root.run_scope.runner)) root.ui.battle.log_view.append_bb(root.Narrator.route_line(candidates, root.run_scope.runner))
# 战斗胜利总结结算(battle.md OQ14):战斗胜利停点先弹 BattleEndOverlay # 战斗胜利总结结算(battle.md OQ14):战斗胜利停点先弹 BattleEndOverlay
# 胜利形态(金蜡封+GP 增量+总量+判定行+「下一节点」+明细回看),确认后进选路页。判据 = 战斗节点 # 胜利形态(金蜡封+GP 增量+总量+判定行+「下一节点」+明细回看),确认后进选路页。判据 = 战斗节点
# 的选路停点且战斗已收场(battle 收尾分支 _settlement 置 null 先于本信号;入口/兑付/商店停点 # 的选路停点且战斗已收场(battle 收尾分支 _settlement 置 null 先于本信号;入口/兑付/商店停点
# 的当前节点非 battle 型——三态天然分流)。Boss 胜/失败走 level_finished 另路不经此处。 # 的当前节点非 battle 型——三态天然分流)。Boss 胜/失败走 level_finished 另路不经此处。
if root.run_scope.is_adventure and root.run_scope.runner.current_battle() == null \ if root.run_scope.is_adventure and root.run_scope.runner.current_battle() == null \
and String(root.run_scope.runner.current_node().get("type", "")) == "battle" and not candidates.is_empty(): and String(root.run_scope.runner.current_node().get("type", "")) == "battle" and not candidates.is_empty():
root._settlement_stop_pending = true root.run_scope.settlement_stop_timer.arm()
root.run_scope.pending_candidates = candidates.duplicate() root.run_scope.pending_candidates = candidates.duplicate()
root._end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点", root.ui.modals.end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点",
root.board_view.get_global_rect(), root.ui.battle.board_zone.board_view.get_global_rect(),
{"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]}) {"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]})
root._open_modal_after_playback(root._end_overlay) root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root._refresh_presentations() root._refresh_presentations()
return return
_enter_level_screen(root) _enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(candidates, root.run_scope.runner) root.ui.modals.level_screen.open_choices(candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root._refresh_presentations() root._refresh_presentations()
static func _on_route_chosen(root: Variant, node_id: int) -> void: static func _on_route_chosen(root: Variant, node_id: int) -> void:
# 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈 # 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈
# 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈 # 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈
# 打开低层时自动隐藏本屏、关闭自动恢复) # 打开低层时自动隐藏本屏、关闭自动恢复)
if not root.level_screen.visible: if not root.ui.modals.level_screen.visible:
root._close_modal(root.level_screen) root.ui.modals.flow.close(root.ui.modals.level_screen)
_exit_level_screen(root) _exit_level_screen(root)
if root.run_scope.runner != null and not root.run_scope.runner.pick_next_node(node_id): if root.run_scope.runner != null and not root.run_scope.runner.pick_next_node(node_id):
push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id) push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id)
@@ -202,15 +202,15 @@ static func _on_route_chosen(root: Variant, node_id: int) -> void:
static func _enter_level_screen(root: Variant) -> void: static func _enter_level_screen(root: Variant) -> void:
root.run_scope.level_screen_active = true root.run_scope.level_screen_active = true
root._battle_root.visible = false root.ui.screens.battle_root.visible = false
root._open_modal(root.level_screen) root.ui.modals.flow.open(root.ui.modals.level_screen)
root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态) root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态)
static func _exit_level_screen(root: Variant) -> void: static func _exit_level_screen(root: Variant) -> void:
root.run_scope.level_screen_active = false root.run_scope.level_screen_active = false
root._battle_root.visible = true root.ui.screens.battle_root.visible = true
if root.level_screen.visible: if root.ui.modals.level_screen.visible:
root._close_modal(root.level_screen) root.ui.modals.flow.close(root.ui.modals.level_screen)
root._sync_status_entry() # 同上 root._sync_status_entry() # 同上
@@ -251,7 +251,7 @@ static func _on_abandon_requested(root: Variant) -> void:
return return
if not root.run_scope.adventure.abandon(): if not root.run_scope.adventure.abandon():
root.logger.log_ux("ux_reject", {"source": "abandon", "sub_reason": "battle_in_flight"}) root.logger.log_ux("ux_reject", {"source": "abandon", "sub_reason": "battle_in_flight"})
root.board_zone.toast.show_toast("战斗进行中——无法放弃", "warn") root.ui.battle.board_zone.toast.show_toast("战斗进行中——无法放弃", "warn")
# ---- 营地覆盖层与钥石账本(写调用唯一归组合根)---- # ---- 营地覆盖层与钥石账本(写调用唯一归组合根)----
+15 -15
View File
@@ -13,7 +13,7 @@ static func _on_diagnose_pressed(root: Variant) -> void:
if root.run_scope.settlement != null: if root.run_scope.settlement != null:
ctx["settlement"] = root.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读) ctx["settlement"] = root.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
var path: String = root.logger.dump_diagnosis(ctx) var path: String = root.logger.dump_diagnosis(ctx)
root.board_zone.toast.show_toast("已转储诊断: " + path, "info") root.ui.battle.board_zone.toast.show_toast("已转储诊断: " + path, "info")
@@ -21,17 +21,17 @@ static func _show_debug_overlay(root: Variant) -> void:
# 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内: # 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内:
# 棋盘输入锁/状态角标判据排除浮窗, # 棋盘输入锁/状态角标判据排除浮窗,
# 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制 # 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制
if root.debug_overlay == null: if root.ui.modals.debug_overlay == null:
return return
root._open_modal(root.debug_overlay) root.ui.modals.flow.open(root.ui.modals.debug_overlay)
_sync_debug_overlay(root) _sync_debug_overlay(root)
# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口) # 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口)
static func _on_debug_badge_pressed(root: Variant) -> void: static func _on_debug_badge_pressed(root: Variant) -> void:
var map_on: bool = root.level_screen != null and root.level_screen.visible var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible
if (map_on and root._modal_flow.badge_greyed_map()) or (not map_on and root._modal_flow.badge_greyed_battle()): if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()):
return return
_show_debug_overlay(root) _show_debug_overlay(root)
@@ -40,10 +40,10 @@ static func _on_debug_badge_pressed(root: Variant) -> void:
# 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合 # 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合
# 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律) # 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律)
static func _sync_debug_overlay(root: Variant) -> void: static func _sync_debug_overlay(root: Variant) -> void:
if root.debug_overlay == null: if root.ui.modals.debug_overlay == null:
return return
root.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放) root.ui.modals.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放)
root.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动) root.ui.modals.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
@@ -63,7 +63,7 @@ static func _debug_run_data(root: Variant) -> Dictionary:
static func _close_debug_overlay(root: Variant) -> void: static func _close_debug_overlay(root: Variant) -> void:
root._close_modal(root.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复 root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void: static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void:
# 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗) # 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗)
@@ -83,11 +83,11 @@ static func _start_debug_levels(root: Variant) -> void:
# settlement/编排态=状态机破坏;模拟在跑时 reset_sliders 会作废测量读数(d5 置灰之外的信号绕过闸) # settlement/编排态=状态机破坏;模拟在跑时 reset_sliders 会作废测量读数(d5 置灰之外的信号绕过闸)
if root.run_scope.is_adventure or root.run_scope.settlement != null or root._tune_sim.is_running(): if root.run_scope.is_adventure or root.run_scope.settlement != null or root._tune_sim.is_running():
return return
root.debug_overlay.reset_sliders() root.ui.modals.debug_overlay.reset_sliders()
root._close_modal(root.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay) root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay)
root._start_overlay.visible = false root.ui.screens.start_overlay.visible = false
root.log_view.clear() root.ui.battle.log_view.clear()
root.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true)) root.ui.battle.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true))
root._begin_session(false) root._begin_session(false)
if not root._debug.start_debug_levels(): if not root._debug.start_debug_levels():
root._fatal_screen("战斗启动失败——见输出日志") root._fatal_screen("战斗启动失败——见输出日志")
@@ -100,6 +100,6 @@ static func _on_debug_battle_created(root: Variant, settlement_ref: BattleOrches
static func _on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void: static func _on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
root.run_scope.settlement = settlement_ref root.run_scope.settlement = settlement_ref
root.board_view.rebuild(root.run_scope.settlement.minesweeper.current_board(), root.run_scope.settlement.minesweeper) root.ui.battle.board_zone.board_view.rebuild(root.run_scope.settlement.minesweeper.current_board(), root.run_scope.settlement.minesweeper)
root._check_board_hints() root._check_board_hints()
root._refresh_presentations() root._refresh_presentations()
+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
+77 -37
View File
@@ -1,16 +1,20 @@
## VERTICAL SLICE - NOT FOR PRODUCTION ## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根覆盖层流程承接件:设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂。 ## 组合根覆盖层流程承接件:设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂。
## 本件是 main.gd 同名函数的逐字搬运(成员访问改 root 前缀)——main 侧留同名薄委托,探针 main.X 路径零改动。 ## 本件是 main.gd 同名函数的逐字搬运(成员访问改 root 前缀)——main 侧留同名薄委托,探针 main.X 路径零改动。
## 全部开合走组合根模态单口(root._open_modal/_close_modal);本件零成员态。 ## 全部开合走组合根模态编排口(root.ui.modals.flow.open/close);本件零成员态。
extends RefCounted extends RefCounted
static func _open_settings(root: Variant) -> void: static func _open_settings(root: Variant) -> void:
root._start_overlay.visible = false root.ui.screens.start_overlay.visible = false
root._open_modal(root.settings_overlay) # 模态栈单口 root.ui.modals.flow.open(root.ui.modals.settings_overlay) # 模态栈单口
static func _on_settings_closed(root: Variant) -> void: static func _on_settings_closed(root: Variant) -> void:
root._close_modal(root.settings_overlay) # 模态栈单口 root.ui.modals.flow.close(root.ui.modals.settings_overlay) # 模态栈单口
root._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
# ---- 局内状态面板·只读(入口角标在两屏装配段;开合与数据喂入归组合根)---- # ---- 局内状态面板·只读(入口角标在两屏装配段;开合与数据喂入归组合根)----
@@ -25,22 +29,58 @@ static func _status_data(root: Variant) -> Dictionary:
return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui) return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui)
static func _open_status_panel(root: Variant) -> void: static func _open_status_panel(root: Variant) -> void:
root._open_modal(root.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项) root.ui.modals.flow.open(root.ui.modals.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
root.status_panel.open_status(_status_data(root)) root.ui.modals.status_panel.open_status(_status_data(root))
static func _on_status_closed(root: Variant) -> void: static func _on_status_closed(root: Variant) -> void:
root._close_modal(root.status_panel) # 模态栈单口 root.ui.modals.flow.close(root.ui.modals.status_panel) # 模态栈单口
static func _open_career(root: Variant) -> void: static func _open_career(root: Variant) -> void:
root._start_overlay.visible = false root.ui.screens.start_overlay.visible = false
root._open_modal(root.career_overlay) # 模态栈单口 root.ui.modals.flow.open(root.ui.modals.career_overlay) # 模态栈单口
root.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta)) root.ui.modals.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta))
static func _on_career_closed(root: Variant) -> void: static func _on_career_closed(root: Variant) -> void:
root._close_modal(root.career_overlay) # 模态栈单口 root.ui.modals.flow.close(root.ui.modals.career_overlay) # 模态栈单口
root._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(开层调用点直接改引) # 生涯页快照投影 —— 承接体见 HudPresenter.career_data(开层调用点直接改引)
@@ -52,20 +92,20 @@ static func _on_career_closed(root: Variant) -> void:
# 开层(模态栈单口;与开始层互斥=career 同款先例)。「进入存档」钮归卡② mn3——本函数为 # 开层(模态栈单口;与开始层互斥=career 同款先例)。「进入存档」钮归卡② mn3——本函数为
# 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。 # 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。
static func _open_save_select(root: Variant) -> void: static func _open_save_select(root: Variant) -> void:
root._start_overlay.visible = false root.ui.screens.start_overlay.visible = false
root._open_modal(root.save_select_overlay) # 模态栈单口 root.ui.modals.flow.open(root.ui.modals.save_select_overlay) # 模态栈单口
root.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager)) root.ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager))
static func _on_save_select_closed(root: Variant) -> void: static func _on_save_select_closed(root: Variant) -> void:
root._close_modal(root.save_select_overlay) # 模态栈单口 root.ui.modals.flow.close(root.ui.modals.save_select_overlay) # 模态栈单口
root._start_overlay.visible = true root.ui.screens.start_overlay.visible = true
# 槽数据快照 —— 承接体见 HudPresenter.save_select_data(开层调用点直接改引) # 槽数据快照 —— 承接体见 HudPresenter.save_select_data(开层调用点直接改引)
# 空槽新档链(Q3 裁乙暂行落点=现役 CampOverlay;卡④ 重立卡后改挂场景图):卡① 创建=激活=白板落盘, # 空槽新档链(卡④ T3 换代:落点=营地场景根——CampOverlay 随四柜台拆解退役):卡① 创建=激活=白板落盘,
# 落点开层。动线序(禁先关后开):先落点开层(开始层保持隐藏),选档层被模态栈遮挡隐藏、不关闭。 # 落点开层。动线序(禁先关后开):先落点开层(开始层保持隐藏),选档层被模态栈遮挡隐藏、不关闭。
static func _on_save_select_new(root: Variant, slot: int) -> void: static func _on_save_select_new(root: Variant, slot: int) -> void:
root.save_manager.slots.create_slot(slot) 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: static func _on_save_select_load(root: Variant, slot: int) -> void:
root.save_manager.slots.activate_slot(slot) root.save_manager.slots.activate_slot(slot)
root._camp_pres.open_camp() root._camp_pres.open_camp()
@@ -85,18 +125,18 @@ static func _on_save_select_load(root: Variant, slot: int) -> void:
static func _on_save_select_delete(root: Variant, slot: int) -> void: static func _on_save_select_delete(root: Variant, slot: int) -> void:
root._confirm_consumer = "delete_slot" root._confirm_consumer = "delete_slot"
root._confirm_consumer_slot = slot root._confirm_consumer_slot = slot
root._open_modal(root.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口) root.ui.modals.flow.open(root.ui.modals.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
root.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消") root.ui.modals.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消")
# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰, # 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,
# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding) # 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding)
static func _on_status_badge_pressed(root: Variant) -> void: static func _on_status_badge_pressed(root: Variant) -> void:
var map_on: bool = root.level_screen != null and root.level_screen.visible var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible
# P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住; # P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住;
# 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点) # 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点)
if (map_on and root._modal_flow.badge_greyed_map()) or (not map_on and root._modal_flow.badge_greyed_battle()): if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()):
return return
_open_status_panel(root) _open_status_panel(root)
@@ -106,17 +146,17 @@ static func _on_status_badge_pressed(root: Variant) -> void:
# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。 # 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。
# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。 # 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。
static func _sync_status_entry(root: Variant) -> void: static func _sync_status_entry(root: Variant) -> void:
if root.status_panel == null or root._modals == null or root.board_zone == null or root.level_screen == null: if root.ui.modals.status_panel == null or root.ui.modals.flow == null or root.ui.battle.board_zone == null or root.ui.modals.level_screen == null:
return return
var map_on: bool = root.level_screen.visible var map_on: bool = root.ui.modals.level_screen.visible
root.board_zone.status_badge.visible = root.run_scope.level_active and not map_on root.ui.battle.board_zone.status_badge.visible = root.run_scope.level_active and not map_on
root.level_screen.status_badge.visible = map_on root.ui.modals.level_screen.status_badge.visible = map_on
# 置灰判据为排除式(调试浮窗开态不计入:对局中浮窗开着时角标仍可点) # 置灰判据为排除式(调试浮窗开态不计入:对局中浮窗开着时角标仍可点)
root.board_zone.status_badge.disabled = root._modal_flow.badge_greyed_battle() # 互斥域谓词 root.ui.battle.board_zone.status_badge.disabled = root.ui.modals.flow.badge_greyed_battle() # 互斥域谓词
root.level_screen.status_badge.disabled = root._modal_flow.badge_greyed_map() root.ui.modals.level_screen.status_badge.disabled = root.ui.modals.flow.badge_greyed_map()
# 调试浮层角标(全局浮层任何 UI 不遮挡——可见性不随场景变,只抬 offset # 调试浮层角标(全局浮层任何 UI 不遮挡——可见性不随场景变,只抬 offset
# 避让:地图屏底栏 85px 常驻,角标抬至底栏上方 16px;其余场景右下角 16px 贴底) # 避让:地图屏底栏 85px 常驻,角标抬至底栏上方 16px;其余场景右下角 16px 贴底)
root.debug_badge.offset_bottom = -101.0 if map_on else -16.0 root.ui.screens.debug_badge.offset_bottom = -101.0 if map_on else -16.0
@@ -131,19 +171,19 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic
root.logger.log_wallet(event, gp, extra) root.logger.log_wallet(event, gp, extra)
if String(event) == "gain": if String(event) == "gain":
root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain) root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain)
if root.shop_overlay.visible and root.run_scope.runner != null: if root.ui.modals.shop_overlay.visible and root.run_scope.runner != null:
if root.run_scope.runner.shop.is_shop_pool(): if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变) root.ui.modals.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else: else:
root.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) root.ui.modals.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root._refresh_presentations() root._refresh_presentations()
static func _on_legend_toggled(root: Variant) -> void: static func _on_legend_toggled(root: Variant) -> void:
if root.legend_overlay.is_open(): if root.ui.modals.legend_overlay.is_open():
root._close_modal(root.legend_overlay) root.ui.modals.flow.close(root.ui.modals.legend_overlay)
else: else:
root._open_modal(root.legend_overlay) root.ui.modals.flow.open(root.ui.modals.legend_overlay)
+163 -123
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION ## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根 UI 装配分步承接件:三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开。 ## 组合根 UI 装配分步承接件:三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开。
## 本件是 main._build_ui 的逐字分步搬运(成员访问改 root 前缀)——main 侧留同名薄委托,装配序与 z 序零改动。 ## 本件是 main._build_ui 的逐字分步搬运(成员访问改 root 前缀)——main 侧留同名薄委托,装配序与 z 序零改动。
## 层序唯一事实源仍是 main.LAYER_CONTRACT + _apply_layer_contract(本件不持有层序逻辑)。 ## 层序唯一事实源=root.ui.LAYER_CONTRACT + root.ui.apply_layer_contract(ui_registry.gd——本件不持有层序逻辑)。
extends RefCounted extends RefCounted
const UiAssembler := preload("res://presentation/ui_assembler.gd") const UiAssembler := preload("res://presentation/ui_assembler.gd")
@@ -35,189 +35,231 @@ static func _build_screens(root: Variant) -> void:
# 屏内 level_screen 与 battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后零视觉差; # 屏内 level_screen 与 battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后零视觉差;
# 视觉共存对逐对验算:legend/battle(图例战斗中开——legend 在冒险屏壳之后=其上 ✓)、 # 视觉共存对逐对验算:legend/battle(图例战斗中开——legend 在冒险屏壳之后=其上 ✓)、
# debug/level_screen(浮窗最后子=其上 ✓)、shop/level_screen(商店节点域弹出——模态均在屏壳上 ✓)。 # debug/level_screen(浮窗最后子=其上 ✓)、shop/level_screen(商店节点域弹出——模态均在屏壳上 ✓)。
root._adventure_screen = AdventureScreenScene.instantiate() root.ui.screens.adventure_screen = AdventureScreenScene.instantiate()
root.add_child(root._adventure_screen) root.add_child(root.ui.screens.adventure_screen)
root._menu_screen = MenuScreenScene.instantiate() root.ui.screens.menu_screen = MenuScreenScene.instantiate()
root.add_child(root._menu_screen) root.add_child(root.ui.screens.menu_screen)
root._start_overlay = root._menu_screen.start_overlay root.ui.screens.start_overlay = root.ui.screens.menu_screen.start_overlay
root._battle_root = root._adventure_screen.battle_root root.ui.screens.battle_root = root.ui.screens.adventure_screen.battle_root
root.stage = root._adventure_screen.stage root.ui.battle.stage = root.ui.screens.adventure_screen.stage
root.log_view = root._adventure_screen.log_view root.ui.battle.log_view = root.ui.screens.adventure_screen.log_view
root.info_panel = root._adventure_screen.info_panel root.ui.battle.info_panel = root.ui.screens.adventure_screen.info_panel
root.board_zone = root._adventure_screen.board_zone root.ui.battle.board_zone = root.ui.screens.adventure_screen.board_zone
root.board_view = root._adventure_screen.board_view # 探针兼容别名(= board_zone.board_view) root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx
root.ap_bar = root._adventure_screen.ap_bar # 探针兼容别名(= board_zone.ap_bar) root.ui.modals.level_screen = root.ui.screens.adventure_screen.level_screen
root.settlement_fx = root._adventure_screen.settlement_fx
root.level_screen = root._adventure_screen.level_screen
static func _build_battle_wiring(root: Variant) -> void: static func _build_battle_wiring(root: Variant) -> void:
# 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接) # 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
root._row_pacer = RowPacer.new() root.ui.screens.row_pacer = RowPacer.new()
root._row_pacer.log_view = root.log_view root.ui.screens.row_pacer.log_view = root.ui.battle.log_view
root._row_pacer.stage = root.stage root.ui.screens.row_pacer.stage = root.ui.battle.stage
root._row_pacer.board_zone = root.board_zone root.ui.screens.row_pacer.board_zone = root.ui.battle.board_zone
root._row_pacer.settlement_fx = root.settlement_fx root.ui.screens.row_pacer.settlement_fx = root.ui.battle.settlement_fx
root._row_pacer.board_view = root.board_view root.ui.screens.row_pacer.board_view = root.ui.battle.board_zone.board_view
root._row_pacer.refresh_request = root._refresh_presentations root.ui.screens.row_pacer.refresh_request = root._refresh_presentations
root._row_pacer.pump_ended = root._on_pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放) root.ui.screens.row_pacer.pump_ended = root.ui.modals.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
root.add_child(root._row_pacer) root.add_child(root.ui.screens.row_pacer)
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替, # 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替,
# 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂) # 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂)
root._treasure_ui = TreasureInteractController.new({ root._treasure_ui = TreasureInteractController.new({
"db": root.db, "meta": root.meta, "info_panel": root.info_panel, "toast": root.board_zone.toast, "db": root.db, "meta": root.meta, "info_panel": root.ui.battle.info_panel, "toast": root.ui.battle.board_zone.toast,
"board_view": root.board_view, "board_view": root.ui.battle.board_zone.board_view,
"settlement_get": func() -> Variant: return root.run_scope.settlement, "settlement_get": func() -> Variant: return root.run_scope.settlement,
"runner_get": func() -> Variant: return root.run_scope.runner, "runner_get": func() -> Variant: return root.run_scope.runner,
"grant_get": func() -> Variant: return root.run_scope.settlement.reward_grant() if root.run_scope.settlement != null else null, "grant_get": func() -> Variant: return root.run_scope.settlement.reward_grant() if root.run_scope.settlement != null else null,
}) })
root.board_zone.action_requested.connect(root._on_board_action) root.ui.battle.board_zone.action_requested.connect(root._on_board_action)
root.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标) root.ui.battle.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标)
root.info_panel.treasure_slot_clicked.connect(root._treasure_ui.on_slot_clicked) # 两步交互第一步 root.ui.battle.info_panel.treasure_slot_clicked.connect(root._treasure_ui.on_slot_clicked) # 两步交互第一步
root.board_zone.stop_pressed.connect(root._on_stop_pressed) root.ui.battle.board_zone.stop_pressed.connect(root._on_stop_pressed)
root.board_zone.refresh_confirmed.connect(root._on_refresh_confirmed) root.ui.battle.board_zone.refresh_confirmed.connect(root._on_refresh_confirmed)
root.board_zone.legend_toggled.connect(root._on_legend_toggled) root.ui.battle.board_zone.legend_toggled.connect(root._on_legend_toggled)
root.board_zone.hover_forbidden.connect(root._on_hover_forbidden) root.ui.battle.board_zone.hover_forbidden.connect(root._on_hover_forbidden)
root.board_zone.chord_attempted.connect(root._on_chord_attempted) root.ui.battle.board_zone.chord_attempted.connect(root._on_chord_attempted)
root.logger = PlaytestLogger.new() root.logger = PlaytestLogger.new()
root.logger.ap_per_round = int(root.db.run.action_economy.ap_per_round) root.logger.ap_per_round = int(root.db.run.action_economy.ap_per_round)
static func _build_overlays(root: Variant) -> void: static func _build_overlays(root: Variant) -> void:
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局 # 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局
# < 商店 < 营地 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗; # < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动) # 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db) var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db)
root.legend_overlay = overlays.legend_overlay root.ui.modals.legend_overlay = overlays.legend_overlay
root.node_overlay = overlays.node_overlay root.ui.modals.node_overlay = overlays.node_overlay
root._end_overlay = overlays.end_overlay root.ui.modals.end_overlay = overlays.end_overlay
root.shop_overlay = overlays.shop_overlay root.ui.modals.shop_overlay = overlays.shop_overlay
root._camp_screen = CampScreenScene.instantiate() root.ui.screens.camp_screen = CampScreenScene.instantiate()
root.add_child(root._camp_screen) root.add_child(root.ui.screens.camp_screen)
root.camp_overlay = root._camp_screen.camp_overlay # 营地场景根装配(布局表经 ConfigDB 单点注入——视图禁自载表路径;CampOverlay 随四柜台拆解退役)
# 营地屏壳插位(夹商店层与设置层之间)由 LAYER_CONTRACT 营地槽统一落位——禁散落 move_child var camp_layout: CampSceneLayoutTable = root.db.presentation.camp_scene_layout
root.legend_overlay.closed.connect(root._on_modal_closed.bind(root.legend_overlay)) root.ui.screens.camp_screen.camp_scene.setup_layers(camp_layout)
root.node_overlay.continue_pressed.connect(root._on_node_continue) root.ui.screens.camp_screen.camp_scene.bind_hotspots(camp_layout)
root.level_screen.route_chosen.connect(root._on_route_chosen) root.ui.modals.camp.scene = root.ui.screens.camp_screen.camp_scene
root.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标) # 营地四子界面(独立全屏页——场景根留底形态;句柄落 ui.modals.camp 子容器——禁平铺回 ui_modals)
root.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口 var forge := ForgeView.new()
root._end_overlay.continue_pressed.connect(root._on_battle_end_continue) 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)
root.ui.modals.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标)
root.ui.modals.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口
root.ui.modals.end_overlay.continue_pressed.connect(root._on_battle_end_continue)
static func _build_aux_panels(root: Variant) -> void: static func _build_aux_panels(root: Variant) -> void:
root.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效) root.ui.modals.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效)
root.add_child(root.settings_overlay) root.add_child(root.ui.modals.settings_overlay)
root.settings_overlay.closed.connect(root._on_settings_closed) root.ui.modals.settings_overlay.closed.connect(root._on_settings_closed)
# 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前; # 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前;
# 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路) # 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路)
root.status_panel = StatusPanel.new() root.ui.modals.status_panel = StatusPanel.new()
root.add_child(root.status_panel) root.add_child(root.ui.modals.status_panel)
root.status_panel.closed.connect(root._on_status_closed) root.ui.modals.status_panel.closed.connect(root._on_status_closed)
# 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前; # 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前;
# 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数 # 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数
# 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮) # 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮)
root.career_overlay = CareerOverlay.new() root.ui.modals.career_overlay = CareerOverlay.new()
root.add_child(root.career_overlay) root.add_child(root.ui.modals.career_overlay)
root.career_overlay.closed.connect(root._on_career_closed) root.ui.modals.career_overlay.closed.connect(root._on_career_closed)
# 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号 # 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号
#(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。 #(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。
root.save_select_overlay = SaveSelectOverlay.new() root.ui.modals.save_select_overlay = SaveSelectOverlay.new()
root.add_child(root.save_select_overlay) root.add_child(root.ui.modals.save_select_overlay)
root.save_select_overlay.closed.connect(root._on_save_select_closed) root.ui.modals.save_select_overlay.closed.connect(root._on_save_select_closed)
root.save_select_overlay.new_slot_requested.connect(root._on_save_select_new) root.ui.modals.save_select_overlay.new_slot_requested.connect(root._on_save_select_new)
root.save_select_overlay.load_slot_requested.connect(root._on_save_select_load) root.ui.modals.save_select_overlay.load_slot_requested.connect(root._on_save_select_load)
root.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete) root.ui.modals.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete)
static func _build_curio(root: Variant) -> void: static func _build_curio(root: Variant) -> void:
# 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进; # 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进;
# 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child) # 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child)
root.curio_overlay = CurioPickOverlay.new() root.ui.modals.curio_overlay = CurioPickOverlay.new()
root.add_child(root.curio_overlay) root.add_child(root.ui.modals.curio_overlay)
# 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入, # 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入,
# 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调) # 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db, root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db,
{"curio_overlay": root.curio_overlay, "end_overlay": root._end_overlay, {"curio_overlay": root.ui.modals.curio_overlay, "end_overlay": root.ui.modals.end_overlay,
"board_view": root.board_view, "toast": root.board_zone.toast}, "board_view": root.ui.battle.board_zone.board_view, "toast": root.ui.battle.board_zone.toast},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager, {"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"adventure_get": func() -> Variant: return root.run_scope.adventure, "adventure_get": func() -> Variant: return root.run_scope.adventure,
"runner_get": func() -> Variant: return root.run_scope.runner, "runner_get": func() -> Variant: return root.run_scope.runner,
"adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure}, "adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure},
{"open_modal": root._open_modal, "open_modal_after_playback": root._open_modal_after_playback, {"open_modal": root.ui.modals.flow.open, "open_modal_after_playback": root.ui.modals.flow.open_after_playback,
"close_modal": root._close_modal, "refresh_presentations": root._refresh_presentations, "close_modal": root.ui.modals.flow.close, "refresh_presentations": root._refresh_presentations,
"fatal_screen": root._fatal_screen, "fatal_screen": root._fatal_screen,
"gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束) "gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束)
root.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed) root.ui.modals.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed)
root.curio_overlay.continue_pressed.connect(root._bonus.on_continue) root.ui.modals.curio_overlay.continue_pressed.connect(root._bonus.on_continue)
root.curio_overlay.refresh_requested.connect(root._bonus.on_refresh) root.ui.modals.curio_overlay.refresh_requested.connect(root._bonus.on_refresh)
static func _build_menu_wiring(root: Variant) -> void: static func _build_menu_wiring(root: Variant) -> void:
# 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3: # 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3:
# 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环) # 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环)
root._menu_screen.entersavebtn.pressed.connect(root._open_save_select) root.ui.screens.menu_screen.entersavebtn.pressed.connect(root._open_save_select)
# 调试入口=UI 最顶层全局浮层角标(任何 UI 不遮挡——菜单/战斗/地图三枚 # 调试入口=UI 最顶层全局浮层角标(任何 UI 不遮挡——菜单/战斗/地图三枚
# 屏内角标由全局浮层承载;连接归组合根,树序/登记面见 LAYER_CONTRACT 末位) # 屏内角标由全局浮层承载;连接归组合根,树序/登记面见 LAYER_CONTRACT 末位)
root.debug_badge = UiAssembler.build_debug_badge(root) root.ui.screens.debug_badge = UiAssembler.build_debug_badge(root)
root.add_child(root.debug_badge) root.add_child(root.ui.screens.debug_badge)
root.debug_badge.pressed.connect(root._on_debug_badge_pressed) root.ui.screens.debug_badge.pressed.connect(root._on_debug_badge_pressed)
# 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发; # 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入) # 购买写调用唯一经 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, root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db,
{"camp_overlay": root.camp_overlay, "shop_overlay": root.shop_overlay, {"camp_scene": root.ui.modals.camp.scene, "forge_view": root.ui.modals.camp.forge,
"toast": root.board_zone.toast, "start_overlay": root._start_overlay, "logger": root.logger}, "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, {"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"runner_get": func() -> Variant: return root.run_scope.runner}, "runner_get": func() -> Variant: return root.run_scope.runner},
{"open_modal": root._open_modal, "close_modal": root._close_modal, {"open_modal": root.ui.modals.flow.open, "close_modal": root.ui.modals.flow.close,
"refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束) "refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束)
root.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy) root.ui.modals.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1 root.ui.modals.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave) root.ui.modals.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
root.camp_overlay.buy_requested.connect(root._camp_pres.on_camp_buy) # 营地信号接线(CampOverlay 退役——四子界面意图信号+场景点选组合根路由+closed 经 bind 携视图)
root.camp_overlay.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle) root.ui.modals.camp.forge.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy)
root.camp_overlay.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy) root.ui.modals.camp.forge.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.camp_overlay.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle) root.ui.modals.camp.forge.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.camp_overlay.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy) # 锻造三意图(写口唯一归 MetaProgression) root.ui.modals.camp.treasure.buy_requested.connect(root._camp_pres.on_camp_buy)
root.camp_overlay.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock) root.ui.modals.camp.treasure.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.camp_overlay.forge_select_requested.connect(root._camp_pres.on_camp_forge_select) root.ui.modals.camp.tree.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.camp_overlay.closed.connect(root._camp_pres.on_camp_closed) root.ui.modals.camp.tree.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root._menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口 root.ui.modals.camp.forge.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.forge))
root._menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单 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) # 退出游戏提级一级菜单
static func _build_debug_and_resolvers(root: Variant) -> void: static func _build_debug_and_resolvers(root: Variant) -> void:
var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(root) var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(root)
root._inter_overlay = inter.overlay root.ui.screens.inter_overlay = inter.overlay
root._inter_label = inter.label root.ui.screens.inter_label = inter.label
inter.next_btn.pressed.connect(root._on_inter_next_pressed) inter.next_btn.pressed.connect(root._on_inter_next_pressed)
# 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末 # 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末
#(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层; #(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层;
# 弹窗只在开始菜单域弹出,绘制序恒最上; # 弹窗只在开始菜单域弹出,绘制序恒最上;
# 诊断转储入口在调试浮窗) # 诊断转储入口在调试浮窗)
root.confirm_dialog = ConfirmDialog.new() root.ui.modals.confirm_dialog = ConfirmDialog.new()
root.add_child(root.confirm_dialog) root.add_child(root.ui.modals.confirm_dialog)
# 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer, # 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer,
# confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工) # confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工)
root.confirm_dialog.confirmed.connect(root._on_confirm_accepted) root.ui.modals.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.confirm_dialog.cancelled.connect(root._on_confirm_declined) 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〕+两屏角标再开+当局数据只读区块; # 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块;
# 诊断转储入口在本浮窗,玩家画面零常驻调试件)。 # 诊断转储入口在本浮窗,玩家画面零常驻调试件)。
# 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态); # 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态);
# 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口) # 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口)
root.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp)) root.ui.modals.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp))
root.add_child(root.debug_overlay) root.add_child(root.ui.modals.debug_overlay)
root._seed_edit = root.debug_overlay.seed_edit # 种子读取面在浮窗(_seed_value 唯一消费点不变) root.ui.modals.debug_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.debug_overlay))
root.debug_overlay.closed.connect(root._on_modal_closed.bind(root.debug_overlay)) root.ui.modals.debug_overlay.apply_requested.connect(root._on_tune_apply)
root.debug_overlay.apply_requested.connect(root._on_tune_apply) root.ui.modals.debug_overlay.simulate_requested.connect(root._on_tune_simulate)
root.debug_overlay.simulate_requested.connect(root._on_tune_simulate) root.ui.modals.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
root.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
# 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位) # 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位)
var resolvers = BoardResolversScript.new(root.db, root.debug_overlay, func() -> Variant: return root.run_scope.settlement) var resolvers = BoardResolversScript.new(root.db, root.ui.modals.debug_overlay, func() -> Variant: return root.run_scope.settlement)
# 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效) # 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效)
root.board_view.set_identity_resolver(resolvers.identity_lookup) root.ui.battle.board_zone.board_view.set_identity_resolver(resolvers.identity_lookup)
# 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取) # 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取)
root.board_view.set_variant_mask_resolver(resolvers.variant_mask_lookup) root.ui.battle.board_zone.board_view.set_variant_mask_resolver(resolvers.variant_mask_lookup)
root.board_view.set_variant_mark_resolver(resolvers.variant_mark_lookup) # flat_damage 未开格角标 root.ui.battle.board_zone.board_view.set_variant_mark_resolver(resolvers.variant_mark_lookup) # flat_damage 未开格角标
# 调试会话(英雄生效值预览回调绑同一解析件——创建晚于浮窗、随 _build_ui 一次接线) # 调试会话(英雄生效值预览回调绑同一解析件——创建晚于浮窗、随 _build_ui 一次接线)
root._debug = DebugSessionScript.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG), root._debug = DebugSessionScript.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG),
func() -> Dictionary: return resolvers.debug_hero_override(), root._seed_value) func() -> Dictionary: return resolvers.debug_hero_override(), root._seed_value)
@@ -228,24 +270,22 @@ static func _build_modal_flow(root: Variant) -> void:
# 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT, # 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT,
# _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、 # _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、
# 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child) # 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child)
# 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable) # 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable——实例随 ui.modals 容器构造建立)
root._modal_flow = ModalFlow.new() root.ui.modals.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible
root._modal_flow.start_visible_get = func() -> bool: return root._start_overlay.visible root.ui.modals.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible
root._modal_flow.inter_visible_get = func() -> bool: return root._inter_overlay.visible root.ui.modals.flow.settlement_get = func() -> Variant: return root.run_scope.settlement
root._modal_flow.settlement_get = func() -> Variant: return root.run_scope.settlement root.ui.modals.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty()
root._modal_flow.rows_empty_get = func() -> bool: return root._row_pacer.pending_rows().is_empty() root.ui.modals.flow.flush_rows = root.ui.screens.row_pacer.flush
root._modal_flow.flush_rows = root._flush_row_queue root.ui.modals.flow.on_open_sync = func() -> void:
root._modal_flow.on_open_sync = func() -> void:
root._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步 root._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步
root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步 root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步
root._modal_flow.on_close_sync = func() -> void: root.ui.modals.flow.on_close_sync = func() -> void:
root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径) root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径)
root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套) root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套)
root._modal_flow.replay_click = root._replay_locked_click root.ui.modals.flow.replay_click = root._replay_locked_click
root._modals = root._modal_flow.stack # 探针 _list 登记序断言缝(ui_struct 登记序=z 序纪律断言)
# 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表) # 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表)
root._apply_layer_contract() root.ui.apply_layer_contract(root)
root._tune_sim = TuneSimController.new(root.db, root.debug_overlay, int(root.db.combat.hero.max_hp)) root._tune_sim = TuneSimController.new(root.db, root.ui.modals.debug_overlay, int(root.db.combat.hero.max_hp))
static func fatal_screen(root: Variant, msg: String) -> void: static func fatal_screen(root: Variant, msg: String) -> void:
var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT) var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT)
+7 -7
View File
@@ -1,14 +1,14 @@
class_name CampScreen class_name CampScreen
extends Control extends Control
# 营地屏壳(营地覆盖层的屏壳容器)。 # 营地屏壳(营地场景根的屏壳容器)。
# 营地覆盖层视图原样构建(CampOverlay 类视图继续代码构建、零 .tscn 化改造——契约 2); # 场景图视图代码构建(CampSceneView 继续代码构建、零 .tscn 化改造);布局表经组合根注入
# 购买/开合接线归组合根经 CampShopPresenter(信号连接只在组合根)。 # (main_ui_build 装配段 setup_layers/bind_hotspots——视图禁自载表路径);购买/开合接线归组合根经
# 可独立实例化(F6/headless 挂载)——V2 探针断言载体。 # CampShopPresenter(信号连接只在组合根)。可独立实例化(F6/headless 挂载)——V2 探针断言载体。
var camp_overlay: CampOverlay # 营地覆盖层(原 build_overlays 营地段迁入——main 组合根句柄指向本屏成员) var camp_scene: CampSceneView # 营地场景图(交互式营地场景根——main 组合根句柄指向本屏成员)
func _ready() -> void: func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入) mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
camp_overlay = CampOverlay.new() camp_scene = CampSceneView.new()
add_child(camp_overlay) add_child(camp_scene)
+44 -6
View File
@@ -2,16 +2,28 @@
## 调整板批量模拟控制器:逐次驱动关卡自动策略(与 headless 扫描同口径),进度/报告经面板 setter 注入。 ## 调整板批量模拟控制器:逐次驱动关卡自动策略(与 headless 扫描同口径),进度/报告经面板 setter 注入。
## 零核心写——表基线复位由组合根在移交前完成,加成施加只走自动策略内部 runner.set_hero_override 单路径。 ## 零核心写——表基线复位由组合根在移交前完成,加成施加只走自动策略内部 runner.set_hero_override 单路径。
## presentation 不 preload smoke:驱动实例由调用方注入(须供与 level_autoplay 同款 run_level 签名),缺省 null=运行时 load 兜底。 ## presentation 不 preload smoke:驱动实例由调用方注入(须供与 level_autoplay 同款 run_level 签名),缺省 null=运行时 load 兜底。
## 逐帧步进:每局恰占一帧——第 0 局在调用点同步段执行,第 i 局(i≥1)在第 i 帧信号发射点执行,收尾恒在第 n 帧发射点;
## n=0 退化路径为调用点同步收尾零帧。
## _batch_finished 信号契约:整批收尾放行——发射时点=报告注入与运行态复位之后;消费方=run() 自身收尾放行,
## 外部 await run(n) 经此挂起至整批完成(协程身份契约)。
extends RefCounted extends RefCounted
const SEED_BASE := 4200 # 模拟种子基(与历史实现同源——同种子可回放) const SEED_BASE := 4200 # 模拟种子基(与历史实现同源——同种子可回放)
const PlaytestLogger := preload("res://presentation/playtest_logger.gd") # 试玩日志(同层依赖——运行时 load 收敛为 preload 常量) const PlaytestLogger := preload("res://presentation/playtest_logger.gd") # 试玩日志(同层依赖——运行时 load 收敛为 preload 常量)
signal _batch_finished
var db var db
var panel # 调试浮窗 setter 面:atk_pct/hp_pct/set_sim_running/set_sim_progress/set_sim_report var panel # 调试浮窗 setter 面:atk_pct/hp_pct/set_sim_running/set_sim_progress/set_sim_report
var _base_hp := 0 # 报告中的"基础 HP"口径注记值 var _base_hp := 0 # 报告中的"基础 HP"口径注记值
var _driver = null # 关卡自动策略驱动(调用方注入;null = 旧行为运行时 load 兜底) var _driver = null # 关卡自动策略驱动(调用方注入;null = 旧行为运行时 load 兜底)
var _running := false # 运行态(run 全程 true——组合根互查守卫用,与面板置灰解耦) var _running := false # 运行态(run 全程 true——组合根互查守卫用,与面板置灰解耦)
var _sim_n := 0 # 本批局数(run 期活成员——批末失效、下次 run 重写)
var _sim_i := 0 # 已执行局数(步进计数)
var _sim_stats: Dictionary[Variant, Variant] = {} # stats 六键束(run 期承载)
var _sim_autoplay = null # 自动策略驱动实例(run 期承载)
var _sim_logger = null # 共享 JSONL 日志会话(run 期承载)
var _finished := false # 批完成旗标(_finish_batch 置位——收尾段同步承载)
func _init(db_ref, panel_ref, base_hp: int, driver = null) -> void: func _init(db_ref, panel_ref, base_hp: int, driver = null) -> void:
@@ -44,12 +56,38 @@ func run(n: int) -> void:
panel.set_sim_progress("模拟中:0/%d …" % n) panel.set_sim_progress("模拟中:0/%d …" % n)
var stats := {"wins": 0, "dmg_all": [], "hp_left_win": [], "hp_boss": [], "rounds_all": [], "death_nodes": {}} var stats := {"wins": 0, "dmg_all": [], "hp_left_win": [], "hp_boss": [], "rounds_all": [], "death_nodes": {}}
var autoplay = _autoplay() var autoplay = _autoplay()
for i in range(n): _sim_n = n
_run_one_level(autoplay, stats, i, panel, logger) _sim_i = 0
panel.set_sim_progress("模拟中:%d/%d …" % [i + 1, n]) _sim_stats = stats
await panel.get_tree().process_frame _sim_autoplay = autoplay
logger.close_session() _sim_logger = logger
panel.set_sim_report(HudPresenter.tune_sim_report(n, panel.atk_pct(), panel.hp_pct(), stats, _base_hp)) _finished = false
if n > 0:
if not panel.get_tree().process_frame.is_connected(_step_frame):
panel.get_tree().process_frame.connect(_step_frame) # 恰好连接一次(迭代 39 先例同款守卫)
_step_frame() # 首步同步执行(第 0 局在调用栈内跑完——与 await 形态对齐)
await _batch_finished
else:
_finish_batch() # n=0:循环零进同步收尾,不挂起(与 await 形态一致零帧)
# 逐帧步进回调:i<n 执行一局+进度串+计数推进(每局恰占一帧);i 达 n 次帧断连→收尾→放行
func _step_frame() -> void:
if _sim_i < _sim_n:
_run_one_level(_sim_autoplay, _sim_stats, _sim_i, panel, _sim_logger)
panel.set_sim_progress("模拟中:%d/%d …" % [_sim_i + 1, _sim_n])
_sim_i += 1
else:
panel.get_tree().process_frame.disconnect(_step_frame) # 先断连防再入
_finish_batch()
_batch_finished.emit()
# 整批收尾单点(收尾四语句同帧同相位:close_session→报告→运行态复位——次序与 run() 尾现行形态逐句对齐)
func _finish_batch() -> void:
_finished = true
_sim_logger.close_session()
panel.set_sim_report(HudPresenter.tune_sim_report(_sim_n, panel.atk_pct(), panel.hp_pct(), _sim_stats, _base_hp))
panel.set_sim_running(false) panel.set_sim_running(false)
_running = false _running = false
+9 -7
View File
@@ -6,8 +6,8 @@ extends RefCounted
# 战斗屏三区(B 战斗画面 / C·D 两列——节点链 # 战斗屏三区(B 战斗画面 / C·D 两列——节点链
# 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548; # 进度由关卡画面本体(选路覆盖层整图)承载,纵向空间归 C/D 底行;
# 无顶栏进度条、无道具面板区)。 # 无顶栏进度条、无道具面板区)。分区几何读 ConfigDb.presentation.battle_layout。
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体)。 # + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体)。
# 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx} # 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
# (返回句柄不含 gp_label/keystone_label——GP/钥石不在战斗屏呈现) # (返回句柄不含 gp_label/keystone_label——GP/钥石不在战斗屏呈现)
@@ -17,20 +17,22 @@ static func build_battle_ui(root: Control, db: ConfigDb) -> Dictionary:
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
root.add_theme_constant_override("separation", 0) root.add_theme_constant_override("separation", 0)
# B 区:战斗画面(全宽 × 252px——常驻双舞台,布局决策 D1) var layout: BattleLayoutTable = db.presentation.battle_layout
var stage := BattleStageView.new(db)
# B 区:战斗画面(全宽 × battle_layout.stage_h——常驻双舞台,布局决策 D1)
var stage := BattleStageView.new(db, layout)
root.add_child(stage) root.add_child(stage)
# C/D 两列(548px——含顶栏让出的 28px 纵向空间) # C/D 两列(高度 = 基座高 − B 区高——底行承载)
var bottom := HBoxContainer.new() var bottom := HBoxContainer.new()
bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL
bottom.add_theme_constant_override("separation", 4) bottom.add_theme_constant_override("separation", 4)
root.add_child(bottom) root.add_child(bottom)
var info_panel := HeroInfoPanelView.new() var info_panel := HeroInfoPanelView.new(layout)
bottom.add_child(info_panel) bottom.add_child(info_panel)
var board_zone := BoardZoneView.new() var board_zone := BoardZoneView.new(layout)
bottom.add_child(board_zone) bottom.add_child(board_zone)
board_zone.ap_bar.set_refresh_cost(int(db.run.action_economy.rap_refresh_cost)) # RAP 读数表值驱动 board_zone.ap_bar.set_refresh_cost(int(db.run.action_economy.rap_refresh_cost)) # RAP 读数表值驱动
+10
View File
@@ -0,0 +1,10 @@
## 战区视图域句柄册:战斗屏 A–D 区视图与结算演出覆盖层的注册面域容器。
## 纯字段无方法;RefCounted 不挂树——节点本体树位与 add_child 归属由组合根与装配承接件负责。
class_name UiBattle
extends RefCounted
var stage: BattleStageView # B 区(含 log_view 明细带)
var log_view: BattleLogView
var info_panel: HeroInfoPanelView # C 区(攻击/AC 数据框+两预留区)
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
+1
View File
@@ -0,0 +1 @@
uid://d35j5hry1f40v
+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
+33
View File
@@ -0,0 +1,33 @@
## 模态层域句柄册:十二个平铺模态句柄+营地交互域子容器(camp)+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。
## RefCounted 不挂树;模态登记唯一口=register_layer(anchors 撑满宿主+排入 ModalStack)。
## flow 随容器构造即建(装配早期注入点经 root.ui.modals.flow.* 取 Callable——晚于 flow 建立时点)。
class_name UiModals
extends RefCounted
var flow: ModalFlow
var legend_overlay: LegendOverlay
var node_overlay: NodeOverlay
var level_screen: LevelMapScreen
var curio_overlay: CurioPickOverlay
var end_overlay: BattleEndOverlay
var shop_overlay: ShopOverlay
var camp: UiCampModals
var settings_overlay: SettingsOverlay
var status_panel: StatusPanel
var career_overlay: CareerOverlay
var save_select_overlay: SaveSelectOverlay
var confirm_dialog: ConfirmDialog
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:
# 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后
# 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors
# ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律)
m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏)
+1
View File
@@ -0,0 +1 @@
uid://dbs4qveqvr5hg
+111
View File
@@ -0,0 +1,111 @@
## 界面注册面:呈现层界面元素清单的唯一事实源——哪些层、什么序、地址在哪。
## 持有三域容器(screens/battle/modals)、LAYER_CONTRACT 合同表、node(key) 合同寻址、
## apply_layer_contract(root) 唯一落位口(全仓唯一 move_child 点)。
## RefCounted 不挂树:零信号连接、零会话期引用、零核心写调用——组合根单点装配不变。
class_name UiRegistry
extends RefCounted
const ModalStack := preload("res://presentation/modal_stack.gd")
# ==================== 视觉层合同(唯一事实源——树序=登记序=z 序;改序只改此表) ====================
# kind: shell=屏壳容器(成员随壳不占独立树位;level_screen 例外另占登记位)/modal=模态层(入登记册,
# tags 随登记)/state=全屏态(占树位不入登记册)/node=非视觉节点(占树位不入登记册)。
# host=宿住屏壳(树序随宿主、登记序按本合同);shell=模态成员的屏壳(成员与屏壳同槽:屏壳占该树位、
# 成员占该登记位)。落位=apply_layer_contract(全仓唯一层序落位点,add_child 之后统一排序+登记)。
const LAYER_CONTRACT: Array[Dictionary] = [
{"key": "adventure_screen", "kind": "shell"},
{"key": "menu_screen", "kind": "shell"},
{"key": "row_pacer", "kind": "node"},
{"key": "legend_overlay", "kind": "modal", "tags": []},
{"key": "node_overlay", "kind": "modal", "tags": []},
{"key": "level_screen", "kind": "modal", "tags": [ModalStack.TAG_SCREEN_STATE], "host": "adventure_screen"},
{"key": "curio_overlay", "kind": "modal", "tags": []},
{"key": "end_overlay", "kind": "modal", "tags": []},
{"key": "shop_overlay", "kind": "modal", "tags": []},
{"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": []},
{"key": "save_select_overlay", "kind": "modal", "tags": []}, # 选档界面(营地交互重做卡③——登记生涯同档)
{"key": "inter_overlay", "kind": "state", "tags": []},
{"key": "confirm_dialog", "kind": "modal", "tags": []},
{"key": "debug_overlay", "kind": "modal", "tags": [ModalStack.TAG_NON_LOCKING]},
{"key": "debug_badge", "kind": "float"}, # 调试入口全局浮层——末位=最顶绘制序;
# float 类不注册 ModalStack(open() 遮挡记账只隐藏登记层)→ 任何 UI 打开都不遮它
]
var screens: UiScreens
var battle: UiBattle
var modals: UiModals
func _init() -> void:
screens = UiScreens.new()
battle = UiBattle.new()
modals = UiModals.new()
# 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT)
func node(key: String) -> Node:
match key:
"adventure_screen": return screens.adventure_screen
"menu_screen": return screens.menu_screen
"row_pacer": return screens.row_pacer
"legend_overlay": return modals.legend_overlay
"node_overlay": return modals.node_overlay
"level_screen": return modals.level_screen
"curio_overlay": return modals.curio_overlay
"end_overlay": return modals.end_overlay
"shop_overlay": return modals.shop_overlay
"camp_screen": return screens.camp_screen
"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
"save_select_overlay": return modals.save_select_overlay
"inter_overlay": return screens.inter_overlay
"confirm_dialog": return modals.confirm_dialog
"debug_overlay": return modals.debug_overlay
"debug_badge": return screens.debug_badge
return null
# 合同装配(_build_ui 尾调用):按 LAYER_CONTRACT 序统一落位——直接子层 move_child 到合同槽、
# 模态层按合同序登记;宿住层(host)树序随宿主只登记;成员层(shell)与屏壳同槽(屏壳占树位、成员占登记位)。
# 本函数是全仓唯一层序落位点(move_child 单口)——改层序只改 LAYER_CONTRACT。
func apply_layer_contract(root: Control) -> void:
var slot := 0
for entry: Dictionary in LAYER_CONTRACT:
var tags: Array[Variant] = entry.get("tags", [])
if entry.has("shell"):
var shell: Node = node(String(entry["shell"]))
var member: Node = node(String(entry["key"]))
if shell != null:
root.move_child(shell, slot)
slot += 1
if member != null:
modals.register_layer(member as Control, tags)
else:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
var layer: Node = node(String(entry["key"]))
if layer == null:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
if entry.has("host"):
modals.register_layer(layer as Control, tags)
continue
root.move_child(layer, slot)
slot += 1
if String(entry["kind"]) == "modal":
modals.register_layer(layer as Control, tags)
+1
View File
@@ -0,0 +1 @@
uid://d1hahbtan1te8
+16
View File
@@ -0,0 +1,16 @@
## 屏壳与全屏容器域句柄册:三屏壳、屏内一级子层、全屏态、浮层角标、排播泵节点的注册面域容器。
## 纯字段无方法;RefCounted 不挂树——节点本体树位与 add_child 归属由组合根与装配承接件负责。
class_name UiScreens
extends RefCounted
const RowPacer := preload("res://presentation/row_pacer.gd") # 排播泵取型(row_pacer.gd 非 class_name 全局)
var menu_screen: Control
var camp_screen: Control
var adventure_screen: Control
var start_overlay: Control
var battle_root: Control
var inter_overlay: Control
var inter_label: Label
var debug_badge: Button
var row_pacer: RowPacer
+1
View File
@@ -0,0 +1 @@
uid://dd8ep7nhmyrdq
+2 -2
View File
@@ -31,8 +31,8 @@ var _arm_timer := ArmedTimer.new() # 弃图刷新确认窗(T3/D3 P4 乙—
const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化) const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化)
func _init() -> void: func _init(layout: BattleLayoutTable) -> void:
custom_minimum_size = Vector2(0, 44) custom_minimum_size = Vector2(0, layout.ap_bar_h)
_arm_timer.expired.connect(_disarm) _arm_timer.expired.connect(_disarm)
var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4)) var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4))
add_theme_stylebox_override("panel", sb) add_theme_stylebox_override("panel", sb)
+9 -7
View File
@@ -4,12 +4,12 @@ class_name BattleLogView
extends PanelContainer extends PanelContainer
const BAND_H_DIG := 24
const BAND_H_SETTLE := 84
const AGG_TYPES := ["reward_ineffective"] const AGG_TYPES := ["reward_ineffective"]
const COL_GOLD := UiKit.HEX_GOLD const COL_GOLD := UiKit.HEX_GOLD
var _db var _db
var _band_h_dig := 0
var _band_h_settle := 0
var _log: RichTextLabel var _log: RichTextLabel
var _summary: Label var _summary: Label
var _hint: Label var _hint: Label
@@ -18,9 +18,11 @@ var _round_rows: Array[String] = [] # 本结算轮行缓冲(上轮摘要
var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count}) var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count})
func _init(db_ref) -> void: func _init(db_ref, layout: BattleLayoutTable) -> void:
_db = db_ref _db = db_ref
custom_minimum_size = Vector2(0, BAND_H_DIG) _band_h_dig = layout.log_band_h_dig
_band_h_settle = layout.log_band_h_settle
custom_minimum_size = Vector2(0, _band_h_dig)
size_flags_vertical = Control.SIZE_SHRINK_END size_flags_vertical = Control.SIZE_SHRINK_END
var vb := VBoxContainer.new() var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 2) vb.add_theme_constant_override("separation", 2)
@@ -29,7 +31,7 @@ func _init(db_ref) -> void:
vb.add_child(head) vb.add_child(head)
# 明细条灰字对深灰底实测 2.63:1 → 暗底双色墨制=暖白族 # 明细条灰字对深灰底实测 2.63:1 → 暗底双色墨制=暖白族
_summary = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM) _summary = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM)
_summary.custom_minimum_size = Vector2(0, BAND_H_DIG - 4) _summary.custom_minimum_size = Vector2(0, _band_h_dig - 4)
_summary.clip_text = true _summary.clip_text = true
_summary.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS _summary.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
head.add_child(_summary) head.add_child(_summary)
@@ -39,7 +41,7 @@ func _init(db_ref) -> void:
_log = RichTextLabel.new() _log = RichTextLabel.new()
_log.bbcode_enabled = true _log.bbcode_enabled = true
_log.scroll_following = true _log.scroll_following = true
_log.custom_minimum_size = Vector2(0, BAND_H_SETTLE - 26) _log.custom_minimum_size = Vector2(0, _band_h_settle - 26)
_log.clip_contents = true _log.clip_contents = true
_log.scroll_active = true _log.scroll_active = true
vb.add_child(_log) vb.add_child(_log)
@@ -53,7 +55,7 @@ func set_mode(mode: String) -> void:
var settle := mode == "settle" var settle := mode == "settle"
_log.visible = settle _log.visible = settle
_summary.visible = not settle _summary.visible = not settle
custom_minimum_size = Vector2(0, BAND_H_SETTLE if settle else BAND_H_DIG) custom_minimum_size = Vector2(0, _band_h_settle if settle else _band_h_dig)
func mode() -> String: func mode() -> String:
+5 -5
View File
@@ -24,11 +24,11 @@ var _viewport: SubViewport
var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点) var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点)
func _init(db_ref) -> void: func _init(db_ref, layout: BattleLayoutTable) -> void:
custom_minimum_size = Vector2(0, 252) custom_minimum_size = Vector2(0, layout.stage_h)
_build_backdrop() _build_backdrop()
_build_world_stage() _build_world_stage()
_build_text_half(db_ref) _build_text_half(db_ref, layout)
_build_companion_layer(db_ref) _build_companion_layer(db_ref)
@@ -75,7 +75,7 @@ func _build_world_stage() -> void:
## Control 文本半区域(round_label / stage_center / log_view 原样保留——C1 永久 Control) ## Control 文本半区域(round_label / stage_center / log_view 原样保留——C1 永久 Control)
func _build_text_half(db_ref) -> void: # type-hint: ignore(db 鸭子型注入——单测以最小 stub 构造) func _build_text_half(db_ref, layout: BattleLayoutTable) -> void: # type-hint: ignore(db 鸭子型注入——单测以最小 stub 构造)
var vb := VBoxContainer.new() var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 4) vb.add_theme_constant_override("separation", 4)
vb.set_anchors_preset(Control.PRESET_FULL_RECT) vb.set_anchors_preset(Control.PRESET_FULL_RECT)
@@ -86,7 +86,7 @@ func _build_text_half(db_ref) -> void: # type-hint: ignore(db 鸭子型注入
add_child(vb) add_child(vb)
_build_narr_row(vb) _build_narr_row(vb)
_build_center_row(vb) _build_center_row(vb)
log_view = BattleLogView.new(db_ref) log_view = BattleLogView.new(db_ref, layout)
vb.add_child(log_view) vb.add_child(log_view)
+44 -368
View File
@@ -1,128 +1,31 @@
## VERTICAL SLICE - NOT FOR PRODUCTION ## VERTICAL SLICE - NOT FOR PRODUCTION
## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),全部程序化 _draw 占位视觉(纹理槽位已留未装载)。 ## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),被动绘制壳:_draw 六步时序器
## +视野迷雾域+微光子件生命周期+格件几何;基座层与标识叠加两绘制域件 _init 自建持有
## (cell_base_layer/cell_mark_overlay——单一变化原因=迷雾规格与格件节点生命周期)。
## 无 class_name(类名缓存面纪律)——使用方经 preload 引用。 ## 无 class_name(类名缓存面纪律)——使用方经 preload 引用。
## 内部状态件(u3 子域拆件——契约 9 职责语句「单格绘制输入集」):本件为被动绘制壳, ## 探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主;
## 状态件 _CellPaintState 承载全部绘制入参(单一变化原因=格面呈现),setter 触发宿主重绘语义 ## hl_color/hl_visible/dot_visible/mutation_mark 探针读 u.face.*。
## 经 _owner 回引保持逐字;探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主。
extends Node2D extends Node2D
const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd") const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd")
const _BASE_LAYER := preload("res://presentation/views/cell_base_layer.gd")
const _MARK_OVERLAY := preload("res://presentation/views/cell_mark_overlay.gd")
# ── D 投影档绘制常量(研究件实测取色——立面高光/双段暗带/地面投影;黑罩 alpha 叠加实测等效)── var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动)
const FACADE_HIGHLIGHT_PX := 2.0 # 口沿高光线厚(像素) set(v):
const FACADE_NEAR_FRAC := 0.16 # 立面近段高(格边比——研究件 10/64) edge = v
const FACADE_FAR_FRAC := 0.25 # 立面远段高(格边比——研究件 16/64) if _glow_layer != null and is_instance_valid(_glow_layer):
const FACADE_NEAR_COL := Color(0, 0, 0, 0.60) # 立面近段黑罩(墙顶 132→52 实测 α≈0.61) _glow_layer.setup(edge, glow_group)
const FACADE_FAR_COL := Color(0, 0, 0, 0.82) # 立面远段黑罩(墙顶 132→24 实测 α≈0.82) queue_redraw()
const PROJECTION_NEAR_COL := Color(0, 0, 0, 0.28) # 投影贴墙段(地面 66→46 实测 α≈0.28) var cell := Vector2i.ZERO # 板坐标(诊断观测面)
const PROJECTION_MID_COL := Color(0, 0, 0, 0.16) # 投影中段(渐隐) # ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
const PROJECTION_EDGE_COL := Color(0, 0, 0, 0.06) # 投影远段(渐隐收边) # 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。
# ── E 格高墙面(圣经 v3.5 冻结①——地面格南邻墙→南部 30% 承接墙面:墙顶纹理原亮度 # 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。
# 直贴零暗档零阴影;墙脚阴线只画地面/墙面交界;(b)-(c) 交界 var glow_group := 0:
# 无边框由 BoardView 侧跳过墙格北向描边承载)── set(v):
const E_FACADE_FRAC := 0.30 # 墙面承接高(格边比 30%) if glow_group != v:
glow_group = v
_sync_glow_layer()
## 单格绘制输入集(被动绘制壳的全部绘制入参——BoardView 直写面;setter 触发宿主重绘语义逐字)
class _CellPaintState extends RefCounted:
var _owner: Node2D = null
var cell := Vector2i.ZERO # 板坐标(诊断观测面)
var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动)
set(v):
edge = v
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
_owner._glow_layer.setup(edge, glow_group)
_owner.queue_redraw()
var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框)
# ── 接触带微光绘制面(可挖域微光画在英雄格周界空间侧——art-bible §3.2 v2.8;
# 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)──
var contact_bands: Array[int] = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边)
var contact_corners: Array[int] = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边)
var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画)
var outline_sides: Array[int] = [] # 墙格地面朝向描边码 0=N/1=S/2=W/3=E(程序侧结构件)
# ── 高墙感 D 投影档(研究件四变体定 D=C+地面投影,ITB 同款高度线索)──
# 南立面 = 墙格南缘内侧「口沿高光线 + 双段暗带」(邻地面格才画,墙群内聚);地面投影 = 北邻墙格
# 在本已开格北缘投渐隐暗带。参数按研究件 64px 拼版实测比例换算(立面双段 0.16/0.25 格边、投影带
# 0.22 格边;坐标推导留档 commit)。素材缺位程序化回退同样服役(结构归程序——art-bible §8.5)。
var facade_south := false: # 南立面开关(BoardView paint 按「南邻已开格」写入)
set(v):
facade_south = v
_owner.queue_redraw()
var proj_band := 0.0: # 地面投影带宽(像素,0 = 不画——BoardView 按北邻墙判定写入)
set(v):
proj_band = v
_owner.queue_redraw()
var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区)
set(v):
badge_glyph = v
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
_owner._glow_layer.set_badge_mask(v != "")
_owner.queue_redraw()
var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝)
set(v):
dot_visible = v
_owner.queue_redraw()
var hl_visible := false: # 焦点高亮(悬停态边框覆盖层)
set(v):
hl_visible = v
_owner.queue_redraw()
var hl_color := UiKit.GOLD: # 高亮色(金目标/封印朱/禁入灰禁红)
set(v):
hl_color = v
_owner.queue_redraw()
var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入)
var bevel := 0.0: # 顶面倒角强度(0=关;档位 0/0.35/0.6/1.0)
set(v):
bevel = v
_owner.queue_redraw()
# ── 素材槽位(现全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)──
var tex_base: Texture2D = null # ① 砖面底
var tex_mutation: Texture2D = null # ② 变异纹带
var tex_glyph: Texture2D = null # ③ 数字
var tex_badge: Texture2D = null # ③' 徽章
var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark
set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示))
if tex_seal != v:
tex_seal = v
_owner.queue_redraw()
var tex_facade: Texture2D = null # ⑥ E 格墙面材质(v3.5——南邻墙顶 center 瓦片同源,BoardView 注入)
var facade_strip := false: # E 格开关(BoardView paint 按「已开格且南邻未开墙」写入——瓦片态专用)
set(v):
facade_strip = v
_owner.queue_redraw()
# 变异角标(程序化,tex_mutation 纹带槽暂留):
# "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记)
# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写):
# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,封印照常计费);
# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形)
var treasure_mark := "":
set(v):
if treasure_mark != v:
treasure_mark = v
_owner.queue_redraw()
var mutation_mark := "":
set(v):
if mutation_mark != v:
mutation_mark = v
_owner.queue_redraw()
# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
# 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。
# 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。
var glow_group := 0:
set(v):
if glow_group != v:
glow_group = v
_owner._sync_glow_layer()
var removed_dim := false: # 中立移除退色层(BoardView paint 按排雷玩法 is_removed 写入)
set(v):
removed_dim = v
_owner.queue_redraw()
var st := _CellPaintState.new()
var glyph := "": # 文字位——数字/雷/封印/人/?(现行视觉封印与数字互斥,同位承载;探针直读冻结) var glyph := "": # 文字位——数字/雷/封印/人/?(现行视觉封印与数字互斥,同位承载;探针直读冻结)
set(v): set(v):
glyph = v glyph = v
@@ -141,268 +44,51 @@ var fog_state := 0: # 探针直读冻结
queue_redraw() queue_redraw()
var _glow_layer: Node2D = null var _glow_layer: Node2D = null
var base = null # 格面基座层绘制域件(_init 自建)
var face = null # 格面标识与叠加层绘制域件(_init 自建)
func _init() -> void: func _init() -> void:
st._owner = self base = _BASE_LAYER.new(self)
face = _MARK_OVERLAY.new(self)
func _draw() -> void: func _draw() -> void:
var rect := Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)) var rect := Rect2(Vector2.ZERO, Vector2(edge, edge))
_draw_base_layer(rect) base.draw_base(self, rect) # 基座层整树(描边/立面/投影/接触带/倒角与其补画序)
_draw_mutation_marks() face.draw_marks(self, rect) # 变异标记
_draw_glyph_layer(rect) face.draw_glyph_layer(self, rect, glyph, glyph_color, plate) # 字形/色牌/徽章/封印
_draw_overlay_layers(rect) face.draw_overlay_under(self, rect) # 点迹+高亮(雾层由宿主夹在其后)
_draw_fog_layer(rect) # 宿主迷雾域(原位——雾夹在 hl 与 removed 之间)
face.draw_removed_dim(self, rect) # 退色层
## ① 砖面底层域(占位 = StyleBoxFlat:格底色 + 暗化描边 + 3px 圆角;瓦片态描边覆盖层;
## 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘。素材缺位回退走 elif 程序化原样,零观感变化)
func _draw_base_layer(rect: Rect2) -> void:
if st.tex_base != null:
draw_texture_rect(st.tex_base, rect, false)
# 轮廓加强(轮廓线不明显):墙格地面朝向边补 2px 深墨线(§8.5 结构归程序——
# 素材 1px 墨线在 45/36px 非整数缩放下视觉衰减,程序线保底;墙-墙内界不画,保墙群内聚;
# 坐标推导:各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
if not st.outline_sides.is_empty():
_draw_outline_sides()
_draw_south_facade()
_draw_north_projection()
_draw_e_facade()
# 瓦片态描边覆盖层:tex_base 在位时 BoardView 传入的 bg_sb
# 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序
# 不变:底 < 纹带 < 数字 < 封印 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化
if st.bg_sb != null:
draw_style_box(st.bg_sb, rect)
_draw_contact_or_fallback(rect)
## 接触带微光或平色回退域(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格):
## 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge];
## 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮)
func _draw_contact_or_fallback(rect: Rect2) -> void:
if st.contact_col.a > 0.0:
_draw_north_projection() # 平色回退+微光并发的英雄格补画(瓦片路径已画——同像素重复无观感差)
_draw_e_facade() # E 格墙面补画(同上——英雄格兼 E 格时瓦片路径已画,重复无害)
_draw_contact_bands()
elif st.bg_sb != null:
draw_style_box(st.bg_sb, rect)
_draw_south_facade() # 平色回退路径——D 投影档两件同样服役(结构归程序)
_draw_north_projection()
_draw_bevel()
## ②③④ 中层域:变异纹带与标记/数字色牌与字形/徽章/封印
func _draw_glyph_layer(rect: Rect2) -> void:
# ② 变异纹带(无程序化纹带——槽位先行,贴图落位即用)
if st.tex_mutation != null:
draw_texture_rect(st.tex_mutation, rect, false)
# ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?")
if glyph != "":
if plate:
_draw_digit_plate()
if st.tex_glyph != null:
draw_texture_rect(st.tex_glyph, rect, false)
else:
_draw_glyph(glyph, glyph_color, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式)
if st.badge_glyph != "":
if st.tex_badge != null:
draw_texture_rect(st.tex_badge, rect, false)
else:
_draw_badge(st.badge_glyph)
# ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图)
if st.tex_seal != null:
draw_texture_rect(st.tex_seal, rect, false)
## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌;
## 牌=后备件——雷/「?」维持牌载保读性(牌上深墨族 12:1);
## 牌尺寸 0.60/0.62 格边居中;封印=未开格不垫牌)
func _draw_digit_plate() -> void:
var pw := st.edge * 0.60
var ph := st.edge * 0.62
var pr := Rect2((st.edge - pw) * 0.5, (st.edge - ph) * 0.5, pw, ph)
draw_rect(pr, UiKit.DIGIT_PLATE, true)
draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0)
## ⑤⑥⑦ 顶层域:路径点迹 + 焦点高亮 + 视野迷雾 + 中立移除退色
func _draw_overlay_layers(rect: Rect2) -> void:
# ⑤ 路径点迹 + 焦点高亮(悬停态覆盖层,不参与命中)
if st.dot_visible:
var ds := 6.0
draw_rect(Rect2(Vector2((st.edge - ds) * 0.5, (st.edge - ds) * 0.5), Vector2(ds, ds)), UiKit.HERO_BLUE, true)
if st.hl_visible:
# 悬停高亮减淡(金框太明显——绘制时 55% alpha,hl_color 属性与探针断言面不动)
draw_rect(Rect2(1, 1, st.edge - 2.0, st.edge - 2.0), Color(st.hl_color, 0.55), false, 2.0) # 2px 内描边
_draw_fog_layer()
# ⑦ 中立移除退色层(退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义;
# 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示危险格(无数字无封印印))
if st.removed_dim:
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0.12, 0.10, 0.09, 0.62), true)
## ⑥ 视野迷雾层(最顶遮罩,§6.5 落文): ## ⑥ 视野迷雾层(最顶遮罩,§6.5 落文):
## 全黑=不透明纯黑盖全部叠层(含封印印/徽章/高亮——封印印记随格隐没,§6.5 显式推论); ## 全黑=不透明纯黑盖全部叠层(含封印印/徽章/高亮——封印印记随格隐没,§6.5 显式推论);
## 记忆态=记忆板素色压平(纹理免疫——色牌/封印印/徽章随板隐没)+数字记忆墨重画(数字可读硬指标 ≥4.5:1) ## 记忆态=记忆板素色压平(纹理免疫——色牌/封印印/徽章随板隐没)+数字记忆墨重画(数字可读硬指标 ≥4.5:1)
func _draw_fog_layer() -> void: func _draw_fog_layer(rect: Rect2) -> void:
if fog_state == 1: if fog_state == 1:
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0, 0, 0, 1.0), true) draw_rect(rect, Color(0, 0, 0, 1.0), true)
elif fog_state == 2: elif fog_state == 2:
_draw_fog_memory() _draw_fog_memory(rect)
## 墙格轮廓域:地面朝向边补 2px 深墨线(各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
func _draw_outline_sides() -> void:
var ow := 2.0
var oc := UiKit.FOG_OUTLINE
for b in st.outline_sides:
var or_ := Rect2()
match int(b):
0: or_ = Rect2(0, 0, st.edge, ow)
1: or_ = Rect2(0, st.edge - ow, st.edge, ow)
2: or_ = Rect2(0, 0, ow, st.edge)
3: or_ = Rect2(st.edge - ow, 0, ow, st.edge)
draw_rect(or_, oc, true)
## 接触带微光域:正交整条边 + 斜向共享角点与两侧半条边
func _draw_contact_bands() -> void:
var band := 4.0
var half := st.edge * 0.5
for b in st.contact_bands:
var r := Rect2()
match int(b):
0: r = Rect2(0, 0, st.edge, band)
1: r = Rect2(0, st.edge - band, st.edge, band)
2: r = Rect2(0, 0, band, st.edge)
3: r = Rect2(st.edge - band, 0, band, st.edge)
draw_rect(r, st.contact_col, true)
for cc in st.contact_corners:
_draw_contact_corner(int(cc), band, half)
## 斜向角点域:角点圆 r=6 贴角点圆心 + 近白芯 r=2.5 提亮 + 半条边(角点沿两条邻边各延伸 edge/2——
## 坐标推导:横带沿 N/S 自角点向板心侧 half;NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进——
## 正交整边时重叠同色无害)
func _draw_contact_corner(cc: int, band: float, half: float) -> void:
var pt := Vector2.ZERO
match cc:
0: pt = Vector2(0, 0)
1: pt = Vector2(st.edge, 0)
2: pt = Vector2(0, st.edge)
3: pt = Vector2(st.edge, st.edge)
draw_circle(pt, 6.0, st.contact_col)
draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, st.contact_col.a * 0.95)))
if cc == 0 or cc == 1:
# N 侧横带:NW 自 x=0、NE 自 x=edge-half
draw_rect(Rect2(pt.x if cc == 0 else pt.x - half, 0.0, half, band), st.contact_col, true)
if cc == 2 or cc == 3:
# S 侧横带:SW 自 x=0、SE 自 x=edge-half
draw_rect(Rect2(pt.x if cc == 2 else pt.x - half, st.edge - band, half, band), st.contact_col, true)
if cc == 0 or cc == 2:
# W 侧竖带:NW 自 y=0、SW 自 y=edge-half
draw_rect(Rect2(0.0, pt.y if cc == 0 else pt.y - half, band, half), st.contact_col, true)
if cc == 1 or cc == 3:
# E 侧竖带:NE 自 y=0、SE 自 y=edge-half
draw_rect(Rect2(st.edge - band, pt.y if cc == 1 else pt.y - half, band, half), st.contact_col, true)
## 顶面倒角域(光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让
## 呼吸微光/主角蓝边框保持最外沿可见;强度随档位)
func _draw_bevel() -> void:
if st.bevel <= 0.0:
return
var base: Color = st.bg_sb.bg_color
var lit: Color = base.lightened(0.22 * st.bevel)
var dark: Color = base.darkened(0.22 * st.bevel)
var t := 2.0
draw_rect(Rect2(t, t, st.edge - t * 2.0, t), lit, true)
draw_rect(Rect2(t, t, t, st.edge - t * 2.0), lit, true)
draw_rect(Rect2(t, st.edge - t * 2.0, st.edge - t * 2.0, t), dark, true)
draw_rect(Rect2(st.edge - t * 2.0, t, t, st.edge - t * 2.0), dark, true)
## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角
func _draw_mutation_marks() -> void:
if st.treasure_mark == "mine_reveal":
var inset := st.edge * 0.14
draw_rect(Rect2(inset, inset, st.edge - inset * 2.0, st.edge - inset * 2.0),
Color(UiKit.RED_MINE, 0.9), false, 2.0)
if st.mutation_mark == "flat_damage":
var mw := st.edge * 0.28
var mpts := PackedVector2Array([Vector2(0, st.edge - mw), Vector2(0, st.edge), Vector2(mw, st.edge)])
draw_colored_polygon(mpts, Color(UiKit.RED_MINE, 0.85))
## 记忆态换装域(demo 口径「地板纹理 + 灰罩 α.66」: ## 记忆态换装域(demo 口径「地板纹理 + 灰罩 α.66」:
## 地板纹理原样直画 + FOG_MEMORY_PLATE α.66 灰罩压顶;素材零改动、格边长零改动。 ## 地板纹理原样直画 + FOG_MEMORY_PLATE α.66 灰罩压顶;素材零改动、格边长零改动。
## 记忆墨 FOG_MEMORY_INK 对合成底归一后(真实渲染面)三关 5.71–5.93:1 ≥4.5:1(§7.2 硬指标; ## 记忆墨 FOG_MEMORY_INK 对合成底归一后(真实渲染面)三关 5.71–5.93:1 ≥4.5:1(§7.2 硬指标;
## 测点行值与素材随动,见 ui_contrast_probe 迷雾段)) ## 测点行值与素材随动,见 ui_contrast_probe 迷雾段))
func _draw_fog_memory() -> void: func _draw_fog_memory(rect: Rect2) -> void:
if st.tex_base != null: if base.tex_base != null:
draw_texture_rect(st.tex_base, Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), false) draw_texture_rect(base.tex_base, rect, false)
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(UiKit.FOG_MEMORY_PLATE, 0.66), true) draw_rect(rect, Color(UiKit.FOG_MEMORY_PLATE, 0.66), true)
if glyph != "": if glyph != "":
_draw_glyph(glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源) face.draw_centered_text(self, rect, glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源)
# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿):
# 坐标推导(自格底边起算):远段=[edge−far, edge],近段=[edge−far−near, edge−far],
# 高光线=[edge−far−near−hl, edge−far−near]——与研究件「墙顶面→高光→暗段→更暗段→格界」下行序一致
func _draw_south_facade() -> void:
if not st.facade_south:
return
var far_h := st.edge * FACADE_FAR_FRAC
var near_h := st.edge * FACADE_NEAR_FRAC
var y_far := st.edge - far_h
var y_near := y_far - near_h
var y_hl := y_near - FACADE_HIGHLIGHT_PX
draw_rect(Rect2(0.0, y_hl, st.edge, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点)
draw_rect(Rect2(0.0, y_near, st.edge, near_h), FACADE_NEAR_COL, true)
draw_rect(Rect2(0.0, y_far, st.edge, far_h), FACADE_FAR_COL, true)
# 地面投影(D 档件二——北邻墙格在本已开格北缘投渐隐暗带,自北缘向南三段衰减):
# 坐标推导:三等分 proj_band,α 0.28/0.16/0.06(研究件实测贴墙 α≈0.28、带末渐融入地面)
func _draw_north_projection() -> void:
if st.proj_band <= 0.0:
return
var seg := st.proj_band / 3.0
draw_rect(Rect2(0.0, 0.0, st.edge, seg), PROJECTION_NEAR_COL, true)
draw_rect(Rect2(0.0, seg, st.edge, seg), PROJECTION_MID_COL, true)
draw_rect(Rect2(0.0, seg * 2.0, st.edge, seg), PROJECTION_EDGE_COL, true)
# E 格高墙面(圣经 v3.5 冻结①——方向稿 draw_facade 移植):本格已开且南邻为墙 →
# 南部 30% 承接墙面。材质 = 墙顶 center 瓦片顶部 30% 区带**原亮度**直贴(零暗档零阴影);
# 墙脚阴线只画地面/墙面交界(strip 顶缘 1px);墙面底缘(格界处)不画任何线——直接接南侧墙顶
# (白边导致两截)。仅瓦片态服役(tex_facade 缺位 = 平色回退原语义不动)。
func _draw_e_facade() -> void:
if not st.facade_strip or st.tex_facade == null:
return
var strip_h := st.edge * E_FACADE_FRAC
var y0 := st.edge - strip_h
var ts := st.tex_facade.get_size()
draw_texture_rect_region(st.tex_facade, Rect2(0.0, y0, st.edge, strip_h),
Rect2(0.0, 0.0, ts.x, ts.y * E_FACADE_FRAC))
draw_rect(Rect2(0.0, y0, st.edge, 1.0), UiKit.E_FACADE_FOOT_COL, true)
# 文字位绘制:水平居中 + 垂直居中(基线 = (格高 − 字高)/2 + 上伸部)
func _draw_glyph(g: String, col: Color, fs: int) -> void:
var f := st.font if st.font != null else ThemeDB.fallback_font
if f == null:
return
var asc := f.get_ascent(fs)
var desc := f.get_descent(fs)
draw_string(f, Vector2(0.0, (st.edge - (asc + desc)) * 0.5 + asc), g,
HORIZONTAL_ALIGNMENT_CENTER, st.edge, fs, col)
# 微光子件生命周期:有组才建、组变即换、无组即撤——作用集 ≤8 格常驻, # 微光子件生命周期:有组才建、组变即换、无组即撤——作用集 ≤8 格常驻,
# 其余格零节点零绘制成本。与父件 _draw() 分离的三条理由见本件头注(命令序列/重绘预算/材质挂点)。 # 其余格零节点零绘制成本。与父件 _draw() 分离的三条理由见本件头注(命令序列/重绘预算/材质挂点)。
func _sync_glow_layer() -> void: func _sync_glow_layer() -> void:
if st.glow_group == 0: if glow_group == 0:
if _glow_layer != null and is_instance_valid(_glow_layer): if _glow_layer != null and is_instance_valid(_glow_layer):
_glow_layer.queue_free() _glow_layer.queue_free()
_glow_layer = null _glow_layer = null
@@ -410,15 +96,5 @@ func _sync_glow_layer() -> void:
if _glow_layer == null or not is_instance_valid(_glow_layer): if _glow_layer == null or not is_instance_valid(_glow_layer):
_glow_layer = _GLOW_LAYER.new() _glow_layer = _GLOW_LAYER.new()
add_child(_glow_layer) add_child(_glow_layer)
_glow_layer.setup(st.edge, st.glow_group) _glow_layer.setup(edge, glow_group)
_glow_layer.set_badge_mask(st.badge_glyph != "") _glow_layer.set_badge_mask(face.badge_glyph != "")
# 徽章绘制:右下角金(文字贴角内缩 2–3px)
func _draw_badge(g: String) -> void:
var f := st.font if st.font != null else ThemeDB.fallback_font
if f == null:
return
var fs := maxi(10, int(st.edge * 0.28))
var sz := f.get_string_size(g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs)
draw_string(f, Vector2(st.edge - sz.x - 2.0, st.edge - 3.0), g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiKit.GOLD)
+130
View File
@@ -0,0 +1,130 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 悬停预承诺呈现域件(BoardView 拆件——单一变化原因=悬停反馈语义):
## 三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚的唯一承载(自 board_view 收编 11 函数族)。
## 无 class_name(类名缓存面纪律)——经宿主 board_view preload 引用;构造=引用束注入
## (cells/minesweeper/diggable 共享引用+格命中换算/壳级光标写入/hover_forbidden 与 chord 发射注入 Callable——
## 输入经宿主三入站口委托,禁反向持宿主引用);_r0 出生展开区成员留宿主。
extends RefCounted
const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金)
const COL_SEAL := UiKit.SEAL_BLACK # 封印黑(与宿主 :658 双侧服役——各自 alias UiKit 单源)
const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体
const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽)
const CHORD_WINDOW_MS := 350
var _cells := {} # 共享引用(宿主容器同对象——rebuild 只 clear 不重绑)
var _minesweeper = null # 排雷玩法 只读查询引用(宿主 rebuild 重绑时同拍回写本成员)
var _diggable := {} # 共享引用(可挖域——呼吸微光作用集同源)
var _cb_cell_at: Callable # 格命中换算(宿主 _cell_at——整图公式单点留宿主)
var _cb_cursor: Callable # 壳级光标写入(宿主 mouse_default_cursor_shape 单点)
var _cb_forbidden: Callable # hover_forbidden 信号发射(首遇 toast 去重归组合根)
var _cb_chord: Callable # chord_attempted 信号发射(chord 肌肉记忆提示锚)
var _hover := Vector2i(-9, -9)
var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000}
func _init(bundle: Dictionary) -> void:
_cells = bundle["cells"]
_minesweeper = bundle["minesweeper"]
_diggable = bundle["diggable"]
_cb_cell_at = bundle["cell_at"]
_cb_cursor = bundle["cursor"]
_cb_forbidden = bundle["forbidden"]
_cb_chord = bundle["chord"]
func _hover_at(pos: Vector2) -> void:
var c: Vector2i = _cb_cell_at.call(pos)
if c.x < 0:
if _hover.x >= 0:
_on_cell_unhover(_hover)
elif c != _hover:
if _hover.x >= 0:
_on_cell_unhover(_hover)
_on_cell_hover(c)
func _on_shell_mouse_exited() -> void:
if _hover.x >= 0:
_on_cell_unhover(_hover)
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
func _detect_chord(c: Vector2i) -> void:
var now := Time.get_ticks_msec()
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
_cb_chord.call(c)
_last_click.ms = -100000
return
_last_click = {"cell": c, "ms": now}
func _on_cell_hover(c: Vector2i) -> void:
_hover = c
_apply_hover_visuals(c)
func _on_cell_unhover(c: Vector2i) -> void:
if _hover == c:
_hover = Vector2i(-9, -9)
_clear_hover_visuals(c)
func _refresh_hover_after_paint() -> void:
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
if _hover.x >= 0 and _cells.has(_hover):
_apply_hover_visuals(_hover)
func _apply_hover_visuals(c: Vector2i) -> void:
if _minesweeper == null or not _cells.has(c):
return
_clear_hover_visuals(c)
var u: Node2D = _cells[c]
var revealed: bool = _minesweeper.is_revealed(c)
var seal: bool = _minesweeper.has_seal(c)
var adjacent: bool = _diggable.has(c)
if revealed:
u.face.hl_visible = true
u.face.hl_color = COL_HL_TARGET # 移动目标格边框(金)
_set_cursor_shape(c, CURSOR_MOVE_SEM)
_show_path_preview(c)
elif adjacent:
u.face.hl_visible = true
u.face.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖)
_set_cursor_shape(c, CURSOR_DIG)
elif seal:
u.face.hl_visible = true
u.face.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
else:
u.face.hl_visible = true
u.face.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
_cb_forbidden.call(c)
func _clear_hover_visuals(c: Vector2i) -> void:
if not _cells.has(c):
return
_cells[c].face.hl_visible = false
_hide_path_preview()
func _set_cursor_shape(c: Vector2i, shape: int) -> void:
_cb_cursor.call(shape) # 壳级光标(悬停格语义决定——单格悬停恒一)
func _show_path_preview(c: Vector2i) -> void:
# 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面)
if _minesweeper == null:
return
var path: Array[Variant] = _minesweeper.compute_move_path(c)
for pc in path:
if _cells.has(pc):
_cells[pc].face.dot_visible = true
func _hide_path_preview() -> void:
for c in _cells:
_cells[c].face.dot_visible = false
@@ -0,0 +1 @@
uid://dojreaughx7tf
+80 -155
View File
@@ -12,6 +12,7 @@ const _CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd")
const _BOARD_WORLD := preload("res://presentation/views/board_world.gd") const _BOARD_WORLD := preload("res://presentation/views/board_world.gd")
const _TILE_PALETTE := preload("res://presentation/views/tile_palette.gd") const _TILE_PALETTE := preload("res://presentation/views/tile_palette.gd")
const _HERO_SPRITE := preload("res://presentation/views/hero_sprite.gd") const _HERO_SPRITE := preload("res://presentation/views/hero_sprite.gd")
const _BOARD_HOVER := preload("res://presentation/views/board_hover.gd")
const COL_BRICK := UiKit.BRICK # 未开——掩尘完整地砖(色板原语 #C9AF83) const COL_BRICK := UiKit.BRICK # 未开——掩尘完整地砖(色板原语 #C9AF83)
const COL_FLOOR := UiKit.FLOOR # 已开——裸土 #B39A78(碎裂裸露地面) const COL_FLOOR := UiKit.FLOOR # 已开——裸土 #B39A78(碎裂裸露地面)
@@ -20,17 +21,13 @@ const COL_HERO := UiKit.HERO_BLUE # 小人边框——小人蓝
const COL_SEAL := UiKit.SEAL_BLACK # 封印黑 #000000 const COL_SEAL := UiKit.SEAL_BLACK # 封印黑 #000000
const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828 const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828
const COL_CONTACT := UiKit.CONTACT_GLOW # 接触带微光基色(蓝族浅一档·近白蓝——§3.2 v2.8) const COL_CONTACT := UiKit.CONTACT_GLOW # 接触带微光基色(蓝族浅一档·近白蓝——§3.2 v2.8)
const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金)
const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键) const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键)
"attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔", "attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔",
"action": "行", # 行 = 行动点型(ap_up/ap_drain 两行) "action": "行", # 行 = 行动点型(ap_up/ap_drain 两行)
"gp": "金", # 金 = GP 型(gp_gain/gp_loss 两行;金色系 ~40° 相位,禁入带紫赤 300–345° 外,取色 UiKit.GOLD 徽标色单点) "gp": "金", # 金 = GP 型(gp_gain/gp_loss 两行;金色系 ~40° 相位,禁入带紫赤 300–345° 外,取色 UiKit.GOLD 徽标色单点)
} }
const BADGE_PLACEHOLDER := "?" # 未知图标键占位纹样(HUD框架:禁静默空图标) const BADGE_PLACEHOLDER := "?" # 未知图标键占位纹样(HUD框架:禁静默空图标)
const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体
const CURSOR_SEAL := Input.CURSOR_POINTING_HAND # 封印——封印形自绘纹理载体 const CURSOR_SEAL := Input.CURSOR_POINTING_HAND # 封印——封印形自绘纹理载体
const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽)
const CHORD_WINDOW_MS := 350
const SEP := 2 # 格间距(格尺寸公式与命中换算共用) const SEP := 2 # 格间距(格尺寸公式与命中换算共用)
# 棋盘立体参数 # 棋盘立体参数
@@ -67,8 +64,6 @@ var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit
var _install_tw: Tween # 入场淡入 tween(rebuild 重入先杀) var _install_tw: Tween # 入场淡入 tween(rebuild 重入先杀)
var _ripple_tws: Array[Tween] = [] # R0 涟漪逐格 tween(rebuild 重入全杀) var _ripple_tws: Array[Tween] = [] # R0 涟漪逐格 tween(rebuild 重入全杀)
var _dig_glow := false # 挖掘期才允许唯一在动元素(art-bible §2.1) var _dig_glow := false # 挖掘期才允许唯一在动元素(art-bible §2.1)
var _hover := Vector2i(-9, -9)
var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000}
var _r0 := {} # 出生展开区 R₀(地图战斗资源 mapping 资格域 = S∖R₀——徽章查询跳过,防 E19 诊断噪音) var _r0 := {} # 出生展开区 R₀(地图战斗资源 mapping 资格域 = S∖R₀——徽章查询跳过,防 E19 诊断噪音)
var _desat_mat: ShaderMaterial var _desat_mat: ShaderMaterial
var _desat_tween: Tween var _desat_tween: Tween
@@ -77,6 +72,7 @@ var _cell := 46.0 # 当前格边长(set_cell_area 按容器尺寸重
# ── 拆出件(状态单点在件内)── # ── 拆出件(状态单点在件内)──
var _palette = _TILE_PALETTE.new() # 瓦片件:装载缓存+查表+明度/墨族静态算法 var _palette = _TILE_PALETTE.new() # 瓦片件:装载缓存+查表+明度/墨族静态算法
var _hero_sprite = _HERO_SPRITE.new() # 英雄精灵件:_init 挂为本件子级(tween 需在树上) var _hero_sprite = _HERO_SPRITE.new() # 英雄精灵件:_init 挂为本件子级(tween 需在树上)
var _hover_comp: _BOARD_HOVER # 悬停预承诺呈现域件:三语义光标/目标格边框/路径预览/chord 首遇锚(_init 建为子件——HeroSprite 挂载先例)
# ── 件成员同名转发(探针直读面零改动:读 _palette.seal_tex、读写 _hero_sprite.tex_idle;其余为随迁状态单点再导出)── # ── 件成员同名转发(探针直读面零改动:读 _palette.seal_tex、读写 _hero_sprite.tex_idle;其余为随迁状态单点再导出)──
# ---- 视野迷雾(纯呈现层;现行事实源=HUD框架「视野迷雾」节 v0.13,渲染规格=art-bible §6.5 v3.5)---- # ---- 视野迷雾(纯呈现层;现行事实源=HUD框架「视野迷雾」节 v0.13,渲染规格=art-bible §6.5 v3.5)----
@@ -120,6 +116,25 @@ func _init() -> void:
_hero_sprite.tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE) _hero_sprite.tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE)
_hero_sprite.tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK) _hero_sprite.tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK)
_palette.load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄同款纪律,禁刷屏) _palette.load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄同款纪律,禁刷屏)
_hover_comp = _BOARD_HOVER.new({
"cells": _cells, "minesweeper": _minesweeper, "diggable": _diggable,
"cell_at": _cell_at, "cursor": _set_shell_cursor,
"forbidden": _emit_hover_forbidden, "chord": _emit_chord_attempted,
})
# ---- 悬停域注入面(件回调宿主的壳级单点——Callable 目标)----
func _set_shell_cursor(shape: int) -> void:
mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一)
func _emit_hover_forbidden(c: Vector2i) -> void:
hover_forbidden.emit(c) ## 非相邻未开格悬停(首遇 toast 归组合根去重)
func _emit_chord_attempted(c: Vector2i) -> void:
chord_attempted.emit(c) ## 双击快开尝试(chord 肌肉记忆提示锚)
func board() -> BoardData: func board() -> BoardData:
@@ -128,7 +143,7 @@ func board() -> BoardData:
func _ready() -> void: func _ready() -> void:
_apply_custom_cursors() _apply_custom_cursors()
mouse_exited.connect(_on_shell_mouse_exited) mouse_exited.connect(_hover_comp._on_shell_mouse_exited)
# 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2); # 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2);
@@ -182,26 +197,22 @@ func set_treasure_overlays(reveal_cells: Array, preview_cells: Array) -> void:
func set_world_font(f: Font) -> void: func set_world_font(f: Font) -> void:
_world.board_font = f _world.board_font = f
for c in _cells: for c in _cells:
_cells[c].st.font = f _cells[c].face.font = f
_cells[c].queue_redraw() _cells[c].queue_redraw()
# D 区可用面积(AP 条之外)→ 格边长 = floor((min(宽, 高) − 预留 − (N−1)×格间距) / N)(battle.md 格尺寸公式) # D 区可用面积(AP 条之外)→ 格边长 = floor((min(宽, 高) − 预留 − (N−1)×格间距) / N)(battle.md 格尺寸公式)
# 公式须补算 (N−1)×2px 网格间距与垂直预留 24px,否则 12×12 板实高溢出可用区 ~6px,下缘被 clip_contents 裁掉。 # 公式须补算 (N−1)×2px 网格间距与垂直预留 24px,否则 12×12 板实高溢出可用区 ~6px,下缘被 clip_contents 裁掉。
# 算式唯一落点 = _cell_for(迭代 46 卡C——探针与单元测试共用该静态函数求值,禁复写算式造第二口径)。
func set_cell_area(avail: Vector2) -> void: func set_cell_area(avail: Vector2) -> void:
if _board == null or _board.width == 0: if _board == null or _board.width == 0:
return return
var n: int = maxi(_board.width, _board.height) _cell = float(_cell_for(maxi(_board.width, _board.height), avail))
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))
_hero_sprite.set_cell_size(_cell, float(SEP)) _hero_sprite.set_cell_size(_cell, float(SEP))
var stride := _cell + float(SEP) var stride := _cell + float(SEP)
for c in _cells: for c in _cells:
var u: Node2D = _cells[c] var u: Node2D = _cells[c]
u.st.edge = _cell u.edge = _cell
u.position = Vector2(c) * stride u.position = Vector2(c) * stride
if _world.hero != null and _hero_sprite.last.x >= 0: if _world.hero != null and _hero_sprite.last.x >= 0:
_place_hero(_hero_sprite.last, _hero_sprite.last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点) _place_hero(_hero_sprite.last, _hero_sprite.last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点)
@@ -209,6 +220,17 @@ func set_cell_area(avail: Vector2) -> void:
_update_world_size() _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 消费) # 悬崖参数下发(网格像素尺寸/步距/列数/材质锚色——BoardWorld._draw 消费)
func _refresh_slab() -> void: func _refresh_slab() -> void:
if _board == null or _board.width == 0: if _board == null or _board.width == 0:
@@ -224,6 +246,7 @@ func _refresh_slab() -> void:
func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_board = board _board = board
_minesweeper = minesweeper _minesweeper = minesweeper
_hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
_palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置) _palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置)
for child in _world.get_children(): for child in _world.get_children():
child.queue_free() child.queue_free()
@@ -234,7 +257,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_contact_unit = null _contact_unit = null
_hero_sprite.last = Vector2i(-99, -99) _hero_sprite.last = Vector2i(-99, -99)
_r0.clear() _r0.clear()
_hover = Vector2i(-9, -9) _hover_comp._hover = Vector2i(-9, -9)
for c in _board.compute_r0(): for c in _board.compute_r0():
_r0[c] = true _r0[c] = true
# 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark; # 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark;
@@ -255,17 +278,17 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
# 逐格构建器(rebuild 建格循环唯一出函面):.new() 与属性装配逐字迁入,循环体只调构建器 # 逐格构建器(rebuild 建格循环唯一出函面):.new() 与属性装配逐字迁入,循环体只调构建器
func _build_cell_unit(c: Vector2i, stride: float) -> Node2D: func _build_cell_unit(c: Vector2i, stride: float) -> Node2D:
var u: Node2D = _CELL_UNIT.new() var u: Node2D = _CELL_UNIT.new()
u.st.cell = c u.cell = c
u.st.edge = _cell u.edge = _cell
u.position = Vector2(c) * stride u.position = Vector2(c) * stride
u.st.bevel = DEPTH_BEVEL # 新建格单元继承 u.base.bevel = DEPTH_BEVEL # 新建格单元继承
u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化 u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化
if _palette.tiles_ok: if _palette.tiles_ok:
# NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档, # NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档,
# 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片 # 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片
# 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化) # 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化)
u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
u.st.font = _world.board_font u.face.font = _world.board_font
_world.add_child(u) _world.add_child(u)
return u return u
@@ -283,7 +306,7 @@ func refresh() -> void:
for y in _board.height: for y in _board.height:
for x in _board.width: for x in _board.width:
_paint_cell(Vector2i(x, y), hero) _paint_cell(Vector2i(x, y), hero)
_refresh_hover_after_paint() _hover_comp._refresh_hover_after_paint()
_apply_contact_bands() _apply_contact_bands()
_update_hero(hero) # 英雄精灵 diff 驱动 _update_hero(hero) # 英雄精灵 diff 驱动
@@ -433,33 +456,18 @@ func _update_world_size() -> void:
# 真实输入路径(Node2D 迁移批 Decision 3):壳层 _gui_input → 整图公式换算 → 复用既有 hover/点选/chord 出口 # 真实输入路径(Node2D 迁移批 Decision 3):壳层 _gui_input → 整图公式换算 → 复用既有 hover/点选/chord 出口
#(悬停族已拆 board_hover 件——本壳留三入站口委托)
func _gui_input(ev: InputEvent) -> void: func _gui_input(ev: InputEvent) -> void:
if _minesweeper == null: if _minesweeper == null:
return return
if ev is InputEventMouseMotion: if ev is InputEventMouseMotion:
_hover_at(ev.position) _hover_comp._hover_at(ev.position)
elif ev is InputEventMouseButton and ev.pressed: elif ev is InputEventMouseButton and ev.pressed:
var c := _cell_at(ev.position) var c := _cell_at(ev.position)
if c.x >= 0: if c.x >= 0:
_on_cell_input(c, ev) _on_cell_input(c, ev)
func _hover_at(pos: Vector2) -> void:
var c := _cell_at(pos)
if c.x < 0:
if _hover.x >= 0:
_on_cell_unhover(_hover)
elif c != _hover:
if _hover.x >= 0:
_on_cell_unhover(_hover)
_on_cell_hover(c)
func _on_shell_mouse_exited() -> void:
if _hover.x >= 0:
_on_cell_unhover(_hover)
func _on_cell_input(c: Vector2i, ev: InputEvent) -> void: func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
if _minesweeper == null or not (ev is InputEventMouseButton and ev.pressed): if _minesweeper == null or not (ev is InputEventMouseButton and ev.pressed):
return return
@@ -468,7 +476,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
action_requested.emit(c, "move") action_requested.emit(c, "move")
else: else:
action_requested.emit(c, "dig") action_requested.emit(c, "dig")
_detect_chord(c) _hover_comp._detect_chord(c)
elif ev.button_index == MOUSE_BUTTON_RIGHT: elif ev.button_index == MOUSE_BUTTON_RIGHT:
if not _minesweeper.has_seal(c): if not _minesweeper.has_seal(c):
action_requested.emit(c, "seal_place") action_requested.emit(c, "seal_place")
@@ -476,89 +484,6 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
# 左键相邻封印格直接挖(规则引擎放行)。 # 左键相邻封印格直接挖(规则引擎放行)。
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
func _detect_chord(c: Vector2i) -> void:
var now := Time.get_ticks_msec()
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
chord_attempted.emit(c)
_last_click.ms = -100000
return
_last_click = {"cell": c, "ms": now}
# ---- 悬停预承诺(pattern 4):三语义光标 + 目标格边框 + BFS 路径预览 ----
func _on_cell_hover(c: Vector2i) -> void:
_hover = c
_apply_hover_visuals(c)
func _on_cell_unhover(c: Vector2i) -> void:
if _hover == c:
_hover = Vector2i(-9, -9)
_clear_hover_visuals(c)
func _refresh_hover_after_paint() -> void:
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
if _hover.x >= 0 and _cells.has(_hover):
_apply_hover_visuals(_hover)
func _apply_hover_visuals(c: Vector2i) -> void:
if _minesweeper == null or not _cells.has(c):
return
_clear_hover_visuals(c)
var u: Node2D = _cells[c]
var revealed: bool = _minesweeper.is_revealed(c)
var seal: bool = _minesweeper.has_seal(c)
var adjacent: bool = _diggable.has(c)
if revealed:
u.st.hl_visible = true
u.st.hl_color = COL_HL_TARGET # 移动目标格边框(金)
_set_cursor_shape(c, CURSOR_MOVE_SEM)
_show_path_preview(c)
elif adjacent:
u.st.hl_visible = true
u.st.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖)
_set_cursor_shape(c, CURSOR_DIG)
elif seal:
u.st.hl_visible = true
u.st.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
else:
u.st.hl_visible = true
u.st.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
hover_forbidden.emit(c)
func _clear_hover_visuals(c: Vector2i) -> void:
if not _cells.has(c):
return
_cells[c].st.hl_visible = false
_hide_path_preview()
func _set_cursor_shape(c: Vector2i, shape: int) -> void:
mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一)
func _show_path_preview(c: Vector2i) -> void:
# 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面)
if _minesweeper == null:
return
var path: Array[Variant] = _minesweeper.compute_move_path(c)
for pc in path:
if _cells.has(pc):
_cells[pc].st.dot_visible = true
func _hide_path_preview() -> void:
for c in _cells:
_cells[c].st.dot_visible = false
# ---- 奖励徽章(已开格身份 icon——封印之下、纹带之上、不进数字区) ---- # ---- 奖励徽章(已开格身份 icon——封印之下、纹带之上、不进数字区) ----
func _update_badge(c: Vector2i) -> String: func _update_badge(c: Vector2i) -> String:
@@ -613,19 +538,19 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var is_hero: bool = c == hero var is_hero: bool = c == hero
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal # 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。 # 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
u.st.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
if removed: if removed:
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结) revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
seal = false seal = false
u.st.tex_seal = null u.face.tex_seal = null
# 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面 # 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面
#(u.st/u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载) #(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载)
var bg: Color = _paint_cell_ink(c, u, revealed, seal) var bg: Color = _paint_cell_ink(c, u, revealed, seal)
_paint_cell_base(c, u, revealed, is_hero, bg) _paint_cell_base(c, u, revealed, is_hero, bg)
_paint_cell_facade(c, u, revealed) _paint_cell_facade(c, u, revealed)
_paint_cell_overlays(c, u, revealed) _paint_cell_overlays(c, u, revealed)
_paint_cell_fog(c, u, revealed, hero) _paint_cell_fog(c, u, revealed, hero)
u.st.removed_dim = removed u.face.removed_dim = removed
u.queue_redraw() u.queue_redraw()
@@ -696,17 +621,17 @@ func _paint_cell_base(c: Vector2i, u: Node2D, revealed: bool, is_hero: bool, bg:
# 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载) # 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载)
u.glyph = "人" u.glyph = "人"
u.glyph_color = COL_HERO u.glyph_color = COL_HERO
u.st.tex_base = tile u.base.tex_base = tile
u.st.bg_sb = sb u.base.bg_sb = sb
# 立面投影段:未开墙格南缘立面/轮廓描边码;已开格北缘投影暗带/E 格高墙面——段内重置与赋值同序 # 立面投影段:未开墙格南缘立面/轮廓描边码;已开格北缘投影暗带/E 格高墙面——段内重置与赋值同序
func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void: func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void:
u.st.outline_sides.assign([]) # typed Array[int](assign 保持引用语义) u.base.outline_sides.assign([]) # typed Array[int](assign 保持引用语义)
u.st.facade_south = false u.base.facade_south = false
u.st.proj_band = 0.0 u.base.proj_band = 0.0
u.st.tex_facade = null u.base.tex_facade = null
u.st.facade_strip = false u.base.facade_strip = false
# 封印槽不在此重置:尾部重置会与此上方条件赋值构成双写踩踏(把封印纹理恒清 null=素材在位时封印不可见); # 封印槽不在此重置:尾部重置会与此上方条件赋值构成双写踩踏(把封印纹理恒清 null=素材在位时封印不可见);
# 上方 else 分支已是全路径清残留,尾部重置系冗余且有害 # 上方 else 分支已是全路径清残留,尾部重置系冗余且有害
var south_n: Vector2i = c + Vector2i(0, 1) var south_n: Vector2i = c + Vector2i(0, 1)
@@ -716,26 +641,26 @@ func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void:
if not revealed: if not revealed:
# 高墙感 D 投影档:南邻地面格 → 墙格南缘立「口沿高光+双段暗带」立面 # 高墙感 D 投影档:南邻地面格 → 墙格南缘立「口沿高光+双段暗带」立面
#(邻地面才画——墙-墙内界保墙群内聚);板外=墙不画 #(邻地面才画——墙-墙内界保墙群内聚);板外=墙不画
u.st.facade_south = south_in and _minesweeper.is_revealed(south_n) u.base.facade_south = south_in and _minesweeper.is_revealed(south_n)
# 轮廓加强:墙格四邻中已开(地面)格朝向记描边码(板外=墙不描——保墙群外轮廓只在墙/地交界) # 轮廓加强:墙格四邻中已开(地面)格朝向记描边码(板外=墙不描——保墙群外轮廓只在墙/地交界)
for d in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]: for d in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]:
var n: Vector2i = c + d var n: Vector2i = c + d
if n.x >= 0 and n.y >= 0 and n.x < _board.width and n.y < _board.height and _minesweeper.is_revealed(n): if n.x >= 0 and n.y >= 0 and n.x < _board.width and n.y < _board.height and _minesweeper.is_revealed(n):
var code := 0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3)) var code := 0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3))
if code == 1 and u.st.facade_south: if code == 1 and u.base.facade_south:
continue # 南缘由立面件承载(高光+双段暗带)——2px 描边让位防三重叠黑 continue # 南缘由立面件承载(高光+双段暗带)——2px 描边让位防三重叠黑
if code == 0 and _palette.tiles_ok: if code == 0 and _palette.tiles_ok:
continue # v3.5 冻结① (b)-(c) 交界无边框:墙面直贴接墙顶(E 格墙面件在邻格承载)——北向描边不画(平色回退态保留描边) continue # v3.5 冻结① (b)-(c) 交界无边框:墙面直贴接墙顶(E 格墙面件在邻格承载)——北向描边不画(平色回退态保留描边)
u.st.outline_sides.append(code) u.base.outline_sides.append(code)
else: else:
# 地面投影:北邻墙格在本已开格北缘投渐隐暗带(ITB 同款高度线索——带宽按格边比例常量) # 地面投影:北邻墙格在本已开格北缘投渐隐暗带(ITB 同款高度线索——带宽按格边比例常量)
if north_in and not _minesweeper.is_revealed(north_n): if north_in and not _minesweeper.is_revealed(north_n):
u.st.proj_band = u.st.edge * WALL_PROJECTION_FRAC u.base.proj_band = u.edge * WALL_PROJECTION_FRAC
# E 格高墙面(圣经 v3.5 冻结①——方向稿 is_facade_cell 口径):已开格南邻未开墙 → 南部 30% # E 格高墙面(圣经 v3.5 冻结①——方向稿 is_facade_cell 口径):已开格南邻未开墙 → 南部 30%
# 承接墙面(材质=同板墙顶 center 瓦片,原亮度直贴);仅瓦片态启用——平色回退保持原语义 # 承接墙面(材质=同板墙顶 center 瓦片,原亮度直贴);仅瓦片态启用——平色回退保持原语义
if south_in and not _minesweeper.is_revealed(south_n) and _palette.tiles_ok: if south_in and not _minesweeper.is_revealed(south_n) and _palette.tiles_ok:
u.st.facade_strip = true u.base.facade_strip = true
u.st.tex_facade = _palette.tiles.get("wall_center") u.base.tex_facade = _palette.tiles.get("wall_center")
# 秘宝/变异叠加段:真视红框/预读徽章/变异角标/奖励徽章/类型微光组号——glyph/digit_col 局部镜像 # 秘宝/变异叠加段:真视红框/预读徽章/变异角标/奖励徽章/类型微光组号——glyph/digit_col 局部镜像
@@ -746,21 +671,21 @@ func _paint_cell_overlays(c: Vector2i, u: Node2D, revealed: bool) -> void:
var glyph: String = u.glyph var glyph: String = u.glyph
var digit_col: Color = u.glyph_color var digit_col: Color = u.glyph_color
if not revealed: if not revealed:
u.st.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else "" u.face.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else ""
if _treasure_preview.has(c) and glyph == "": if _treasure_preview.has(c) and glyph == "":
var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null
if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))): if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))):
glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))]) glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))])
digit_col = COL_GOLD digit_col = COL_GOLD
u.st.badge_glyph = glyph u.face.badge_glyph = glyph
# ②' 变异角标:未开格带伤害型变异 → 危险红角标(已开=威胁已兑现标记退位); # ②' 变异角标:未开格带伤害型变异 → 危险红角标(已开=威胁已兑现标记退位);
# 回调缺位 = 无标记(程序化角标,tex_mutation 纹带槽暂留) # 回调缺位 = 无标记(程序化角标,tex_mutation 纹带槽暂留)
if not revealed and _vmark_cb.is_valid(): if not revealed and _vmark_cb.is_valid():
u.st.mutation_mark = String(_vmark_cb.call(c)) u.face.mutation_mark = String(_vmark_cb.call(c))
else: else:
u.st.mutation_mark = "" u.face.mutation_mark = ""
u.st.badge_glyph = _update_badge(c) u.face.badge_glyph = _update_badge(c)
u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
# 迷雾段:灯光区外未开格全黑/已开格记忆态灰显——纯呈现零逻辑(🔴 契约 1) # 迷雾段:灯光区外未开格全黑/已开格记忆态灰显——纯呈现零逻辑(🔴 契约 1)
@@ -803,7 +728,7 @@ func _sync_glow_to_units() -> void:
for c in touched: for c in touched:
var u: Node2D = _cells.get(c) var u: Node2D = _cells.get(c)
if u != null: if u != null:
u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE)) u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
func _process(delta: float) -> void: func _process(delta: float) -> void:
@@ -815,14 +740,14 @@ func _process(delta: float) -> void:
if UiKit.motion_scale < 1.0: if UiKit.motion_scale < 1.0:
return # 减动效:呼吸停格(refresh/enable 时已施静态色——静态可辨、无持续运动) return # 减动效:呼吸停格(refresh/enable 时已施静态色——静态可辨、无持续运动)
var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线) var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线)
_contact_unit.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) _contact_unit.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
_contact_unit.queue_redraw() _contact_unit.queue_redraw()
# 带几何写入(refresh / set_dig_glow_enabled(true) 挂点):清旧承载件→按英雄格可挖域写边/角码→施色 # 带几何写入(refresh / set_dig_glow_enabled(true) 挂点):清旧承载件→按英雄格可挖域写边/角码→施色
func _apply_contact_bands() -> void: func _apply_contact_bands() -> void:
if _contact_unit != null and is_instance_valid(_contact_unit) and (_cells.get(_minesweeper.hero_pos()) != _contact_unit if _minesweeper != null else true): if _contact_unit != null and is_instance_valid(_contact_unit) and (_cells.get(_minesweeper.hero_pos()) != _contact_unit if _minesweeper != null else true):
_contact_unit.st.contact_col = Color(0, 0, 0, 0) _contact_unit.base.contact_col = Color(0, 0, 0, 0)
_contact_unit.queue_redraw() _contact_unit.queue_redraw()
_contact_unit = null _contact_unit = null
if _board == null or _minesweeper == null or not _dig_glow: if _board == null or _minesweeper == null or not _dig_glow:
@@ -839,19 +764,19 @@ func _apply_contact_bands() -> void:
for d in [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)]: for d in [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)]:
if _diggable.has(_minesweeper.hero_pos() + d): if _diggable.has(_minesweeper.hero_pos() + d):
corners.append(0 if (d.x < 0 and d.y < 0) else (1 if d.x > 0 and d.y < 0 else (2 if d.x < 0 else 3))) corners.append(0 if (d.x < 0 and d.y < 0) else (1 if d.x > 0 and d.y < 0 else (2 if d.x < 0 else 3)))
hu.st.contact_bands = bands hu.base.contact_bands = bands
hu.st.contact_corners = corners hu.base.contact_corners = corners
# 减动效:呼吸停格——refresh/enable 重放时施**静态**色(本件头注「已施静态色」的落実); # 减动效:呼吸停格——refresh/enable 重放时施**静态**色(本件头注「已施静态色」的落実);
# 0.5 = 呼吸周期中值(正常态 k 的均值)。否则减动效下每次 refresh 都会按当前相位重施色, # 0.5 = 呼吸周期中值(正常态 k 的均值)。否则减动效下每次 refresh 都会按当前相位重施色,
# 带色在 refresh 间跳变=持续运动假象,且令 on/off 双帧取证无法隔离单一变量。 # 带色在 refresh 间跳变=持续运动假象,且令 on/off 双帧取证无法隔离单一变量。
var k := 0.5 if UiKit.motion_scale < 1.0 else 0.5 + 0.5 * sin(_breath_t * 3.0) var k := 0.5 if UiKit.motion_scale < 1.0 else 0.5 + 0.5 * sin(_breath_t * 3.0)
hu.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) hu.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
hu.queue_redraw() hu.queue_redraw()
func _reset_breath() -> void: func _reset_breath() -> void:
if _contact_unit != null and is_instance_valid(_contact_unit): if _contact_unit != null and is_instance_valid(_contact_unit):
_contact_unit.st.contact_col = Color(0, 0, 0, 0) _contact_unit.base.contact_col = Color(0, 0, 0, 0)
_contact_unit.queue_redraw() _contact_unit.queue_redraw()
@@ -898,9 +823,9 @@ func diggable_probe() -> Dictionary:
return _diggable.duplicate() return _diggable.duplicate()
# 悬停格只读出口(case① 悬停到达断言消费面) # 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化)
func hover_probe() -> Vector2i: func hover_probe() -> Vector2i:
return _hover return _hover_comp._hover
# 迷雾参数只读快照(case⑮ 参数漂移断言消费面) # 迷雾参数只读快照(case⑮ 参数漂移断言消费面)
@@ -926,5 +851,5 @@ func cell_rect_probe(c: Vector2i) -> Rect2:
if host.size.x <= 0.0 or host.size.y <= 0.0 or vp_size.x <= 0.0 or vp_size.y <= 0.0: if host.size.x <= 0.0 or host.size.y <= 0.0 or vp_size.x <= 0.0 or vp_size.y <= 0.0:
return Rect2() return Rect2()
var k: Vector2 = host.size / vp_size var k: Vector2 = host.size / vp_size
var local := Rect2(u.global_position, Vector2(u.st.edge, u.st.edge)) var local := Rect2(u.global_position, Vector2(u.edge, u.edge))
return Rect2(host.position + local.position * k, local.size * k) return Rect2(host.position + local.position * k, local.size * k)
+4 -4
View File
@@ -18,17 +18,17 @@ signal hover_forbidden(cell: Vector2i)
signal chord_attempted(cell: Vector2i) signal chord_attempted(cell: Vector2i)
func _init() -> void: func _init(layout: BattleLayoutTable) -> void:
size_flags_horizontal = Control.SIZE_EXPAND_FILL size_flags_horizontal = Control.SIZE_EXPAND_FILL
var vb := VBoxContainer.new() var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 4) vb.add_theme_constant_override("separation", 4)
add_child(vb) add_child(vb)
_build_views(vb) _build_views(vb, layout)
## 视图三件域:AP 条+棋盘区(含动效与 toast 叠层) ## 视图三件域:AP 条+棋盘区(含动效与 toast 叠层)
func _build_views(vb: VBoxContainer) -> void: func _build_views(vb: VBoxContainer, layout: BattleLayoutTable) -> void:
ap_bar = ApBarView.new() ap_bar = ApBarView.new(layout)
vb.add_child(ap_bar) vb.add_child(ap_bar)
var board_area := Control.new() var board_area := Control.new()
board_area.size_flags_vertical = Control.SIZE_EXPAND_FILL board_area.size_flags_vertical = Control.SIZE_EXPAND_FILL
@@ -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
Loaded 100 of 210 files, more files were not shown because too many files have changed in this diff. Show more