Compare commits

...
Author SHA1 Message Date
DKChan 2e5eba3122 迭代46收尾(审计报告落位+实现报告补审计结论节与附加发现处置) 2026-10-31 15:12:27 +08:00
DKChan da0c60323a 迭代46收尾文档(实现报告+反哺点): 卡A/B收口·卡C停摆(L3#2实板可读面不达标——14×14基座档28px<30px,停机呈报落主区reports)·实际对照表V1-V12与卡B六行·结构预算S13前后·目检五项登记·实现决定十一条备案·反哺点九条(奇物/行动经济/战斗结算/地图战斗资源/architecture三行/art-bible两行/battle.md随裁决/术语表零增) 2026-10-31 15:12:25 +08:00
DKChan 1c9f5d36b1 迭代46卡B(名字行提档与地台色合并): 名字行五取值点FS_CAPTION→FS_HINT(12→14·消散提示:114合法位零触碰)·_name_baseline偏移17→18(0.35h字号档推导14×0.35≈5,T0实测ts=(14,20)记录)·name_line_probe增name_font_size键·ui_kit删STAND_STONE_EDGE/STAND_STONE_RIM两常量+原位增PLAZA_EDGE=Color8(76,64,56)·三绘制点改指(描边→PLAZA_EDGE·顶缘线/口沿高光→BRICK)·standee测试V6换代149→150(9px档→8px档)+V10/V11/V12增量(探针实读/两绘制点源码锚/FS_CAPTION计数锚==1/常量表退役零键/BRICK·PLAZA_EDGE逐位/改指源码锚)·golden三张0.00%留痕(换值色差≤5低于容差≤16——重锚无需执行)·grep退役常量仅剩测试断言串(预期例外)·26 checks全绿·gdstyle零新增键 2026-10-31 15:12:24 +08:00
DKChan b0b8c9ebd0 迭代46卡A-T8修正(审计处置): 两新件.uid边车入库+S8_FILES与S7_B1_SET同步增补battle_refresh_flow(新件零await,S8/S7实跑绿)——审计附加发现①②处置 2026-10-31 15:12:18 +08:00
DKChan 45b53d6897 迭代46卡A-T8修正(S7): S7_B1_SET增补battle_refresh_flow.gd——换图刷新流程件=C13拆分边界的B1域拆件(承载旧资源retire_mapping/装板编排调用,卡A§八明列新结算族件);判据口径不变(B2/B3/B4对外API仍限B1域调用);architecture模块归属BattleRefreshFlow行走反哺点 2026-10-31 08:34:07 +08:00
DKChan f498d2ec85 迭代46卡A-T8修正(S9): 四处显式非参数化容器注解改:=推断(R1无类型容器——emit返回值承接形态;零行为改动) 2026-10-31 08:24:26 +08:00
DKChan bf7aa514b6 迭代46卡A-T6(数据行与牌库计数): adventure_feats换代37行(普通14/优秀13/稀有10/传说0·牌库102+37=139——旧30行断言按实数换代零放宽)·V11专项(validate零违例·七行稀有度cap对表·desc禁词·七件计数器引用闭合[value_counter/when-id全有写入方——预存行消费点记账形态不入判据域]·三计数器在册)·refresh_feats 85 checks绿·adventure_feats 138 checks绿 2026-10-31 08:08:37 +08:00
DKChan 208e5652db 迭代46卡A-T5(五件后续效果): 授予段round_end heal value_counter兑付(坑道小憩——units.heal封顶·满血未生效行·先于set清零行消费层值)·石层缓冲/蓄力周转全走既有消费面(统一damage_taken与attack_bonus value_counter零新码)·feats.tres+3行(石层缓冲common/蓄力周转excellent/坑道小憩common·trigger结构按卡§十.T6)·V6全局固定减伤(pierce8→6双刷不叠加·未刷新轮不减·地形max(下限,ev−2))·V9轮末恢复(白盒预置上限3闸·回复3层清0·满血未生效·自然累计2)·V8蓄力消费(攻击后清0·战斗新建0)·V7延迟群伤(刷新零伤害行·战斗段末×2)·窄测三件全绿(44/70/134) 2026-10-31 08:06:06 +08:00
DKChan be7ef54e30 迭代46卡A-T4(成功事实与命中施加): feats.tres+2行(崩壁冲击/矿道红利——群伤open_ticket与grant_gp订阅面)·V4成功事实专项(非Active/每轮上限/RAP不足三拒面零事实零换图——成功恰一次经probe_br_counter计数与board_changed双观测)·V10矿道红利专项(escrow每次成功+1·fx_grant_gp行在案·拒绝零变化)·refresh_feats 48 checks全绿 2026-10-31 07:56:07 +08:00
DKChan c062b62d12 迭代46卡A-T3(两件基座): action_economy dig_paid命中环累计grant_reserve_ap(扣费受理后一次性入池·连锁不二次·拒绝轮零入池)+request_refresh收折后价入参(缺省-1=表基线逐位不变)·新建BattleRefreshFlow(可用性门/折价扣费/换图/成功事实——C13拆分边界,_effective_refresh_cost声明序第一命中floor(base×mult/100),can与执行同源)·编排器两公开方法单行转发(删旧刷新体,C10行数只降)·授予段apply_board_refreshed成功事实单点+奖励授予件defer_ticket案C同队列(类变量零增员)+来源豁免fx_board_refresh·feats.tres+2基座行(矿脉储能/省力周转·excellent·cap1)·T3四专项+V12侧全绿·round_flow 134 checks回归绿 2026-10-31 07:52:14 +08:00
DKChan b44d3234b8 迭代46卡A-T2(全局damage_taken统一): 新增_emit_hero_damage_taken唯一发射口(payload必含kind/amount/source/cell/round/hp_ratio)·_enemy_shield_apply改名_apply_hero_damage_effects=唯一消费族(组合次序shield→damage_reduce→ac_bonus→counter_add冻结)·敌方攻击改走统一口(掷点后交付前时点不变)·_resolve_board_flat_hero下限前接入(hero_damage_consumed标记闸防重复)·地形路径并入全局消费族并删本地_terrain_damage_reduce(全局受伤语义=shield同面消费)·私有可能净增1≤2·refresh_feats_test新建V5两专项(四路径逐路恰一次+无重复消费,临时承伤计数专长经ConfigDB注入)·窄测refresh_feats/damage_controller/seal_polarity/round_flow全绿(44/70/134 checks) 2026-10-31 07:44:24 +08:00
DKChan 94a05afd49 迭代46卡A-T1(词表扩签): EVENTS+refresh_request/board_refreshed(20→22)·MINE_EVENTS同步换图刷新面·EFFECT_KINDS+grant_reserve_ap/apply_discount(32→34)·零fact/op/DO_KEYS新增·forge_table计数断言20/32→22/34换代·effect_words新增两事件两效果在册断言(旧出域负例全保留)·孤立验证两件全绿(20 checks/exit0) 2026-10-31 07:35:16 +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
DKChan 14054d9dfb 注销(待办): 技术债观察登记条目整条删除(lambda连接防回归)——三层社区标准方案已闭环:①预防层code-conventions §4.1两条明文纪律在册(多行lambda禁嵌match/信号处理体一律命名方法+bind;捕获循环变量先落地再捕获)②体外扫描层error_scan.py三段扫日志命中即红在役③捕获语义类静默错误由测试断言承接;静态层确有缝(gdstyle 23条quality规则无lambda专项)但本事项仅是数数观察不拦任何坑,补缝唯一方式=闸件加规则,而危险形态(多行lambda嵌match)全库117处实测零存量、登记半月零事故,为零命中形态写闸件属过度工程;社区论证凭据=官方提案godot-proposals #4736永久拒绝try/catch、错误码哲学为设计决策非版本bug 2026-10-30 06:38:33 +08:00
DKChan d2f282d9bc 登记(待办): 棋盘封印印记图形换代一条——现件 seal_mark.png 仍是经典扫雷旗形(旗面+旗杆),2026-09-28 只换色未换形;用户欲改法阵或锁链,形未定故只登记待美术设计,缺口列五待裁点(形/封印黑不变槽是否松动/32px 内是否侵入数字字形区/战末金蜡封徽记与悬停印记形光标两处连带件/与施印特效+印记常显两件的合并关系),实况记消费面两处与文档侧仍写「旗」的口径点(art-bible §4.1 旗杆前提+§3.2、art-assets-checklist §3.3/§4、battle.md:128/:270),改动面=文档+美术素材;格式与退役词闸全绿 2026-10-30 06:16:13 +08:00
DKChan ab3b4a9d80 同步(工程批收尾): test-conventions §7覆盖率句两处旧口径随a2ec3e8b裁决换代——「是否引入待办清单在册待裁」改直陈不引入(条目已删裁决已落),「现行补洞=零指名单测清单」改能力清单覆盖域表(零指名机制已被能力清单模型取代);规约修订经doc-design入口落笔,零待裁点,用户已发令;断链面已核仅此一处 2026-10-30 06:10:21 +08:00
DKChan fbed817fbe 注销(待办): qa/收尾孤儿遗留余二项整条删除——第5项字面量墨收测前置登记两头失效:主任务(iter19卡②墨色收测)随卡退役无现役承接,前置立常量不成立(实测bcard四处色值仅选中底#8A5A34在art-bible色板在册[木色§7.2],✓章#3A2A08/描述#4A3A28/脚注#6E4534三处游离色板外,真要动第一步是美术线色板准入/换色合并非ui_kit常量化);第7项商店房截图纳屏诉求剩余面只剩像素级(结构面ui_struct_probe _audit(shop)与交互面ui_click_probe已覆盖),像素面shot_probe头注已定谳不纳屏(真实路径不可达+合成投喂违反该探针口径),真缺口(商店屏文字对比度无机检)按未实测不启用不立新条,试玩出观感问题再按当时形态登记 2026-10-30 06:00:04 +08:00
DKChan 0caa2f5b7c 刷新(待办): 技术债观察条目长函数半边核销——点名四件(level_map_screen选路装配原155行/camp_overlay_view原152行/debug_overlay原114行/treasure_item_table schema校验群)实测现各文件最长函数仅25-44行全部消解,S13质量闸扫面覆盖core+data+presentation基线冻结禁恶化已接管防回归;条目缩为lambda连接观察(76处读数归属2026-10-15,测试面其后多轮重组分布口径未重测) 2026-10-30 05:54:45 +08:00
DKChan 30fdec1a62 写卡(迭代 44 卡③测速循环批 v1.1 停卡重裁): 实现段 T0 三重证据定谳卡③核心前提已被迭代 41 测试债批消化——215da315 提交信息明载「tune_sim 运行态收尾归还 run」+wd8 三断言(现位 :145-147)与 ui_tune_probe 全链路实跑两面全绿(停卡呈报=iter44 worktree reports/iter44-实现报告-2026-10-30.md §5),按停卡呈报建议案重写卡片——剥离已申报行为改动 V3(收尾置假还原无对象,还原已由迭代 41 完成并申报;完成定义改「diff 中行为改动零处」,对照表 V3 行改定义为非行为改动行=收尾四语句同帧同相位迁入 _finish_batch 的回归锚面,wd8/probe 判据「零改动转绿」改「零改动保持绿」),T0 义务改现状复核(两面实跑应绿——绿=按无行为改动形态开工,红=停卡呈报),夹具=现行产品码 await 形态逐字副本、与产品码新形态申报差异两处减一处(只余夹具暴露 stats 供对拍),全部 file:line 按 2026-10-30 master 重锚(run :36-54/_run_one_level :58-68 尾部实为败局记账/wd8 :139-147/基线键 :15;S8 九文件漂移订正十文件),architecture v1.25 S13 注销句保持(注销对象是 await 豁免不依赖行为改动申报,句内申报半句随实现批现状化——同步面注记),§16/§17/§13 随动;待办清单条目实况同批订正该失效申报句并登记重裁凭据(状态行维持已写卡+已编迭代 44——v1.1 落定即重立),roadmap 迭代 44 行不动 2026-10-30 05:52:44 +08:00
DKChan a2ec3e8b94 工程(渲染闸脚本错误扫描): 三段探针日志逐段过 tools/error_scan.py 补三硬闸之二——shot(shot_checker 之后)/fx/board 各一次,命中或日志不可读即红,标记与 run_smoke 同源;头注补⑤与「新增日志段必须同步加扫描」纪律
验证=合成日志三分支(clean 0/命中 1/不可读 2)+真跑 render_gate exit=0 全绿(三段零命中、残留 Godot=0、golden changed 0.00%)+文档域闸五件 PASS。
文档同步=architecture 测试域两处「SCRIPT ERROR 扫描缺失」换代、game/tests/README.md 工具行补判定面。
清单=「测试域遗留」整条删除(第 1 项被能力清单模型取代/第 3 项与 test-conventions §6 存量不清扫口径冲突且多进程分片后前提消失/第 4 项行覆盖率用户裁无意义/第 2 项即本批);基线条目扩为 02/05/09 三键(05 主区实测绿,baseline 红键属代码面,随下一代码批在 worktree 内重生成)
2026-10-30 05:39:33 +08:00
DKChan 2a5174d2a9 文档设计(美术出图登记轻量化): 出图登记义务四字段(模型名/分辨率/seed/对话日期)缩为必注两项「用途+生成时间(YYYY-MM-DD HH:MM)」,参数有则记、无则不填,登记不再作正稿遴选前置——旧口径卡住用户在途出图管线(线上对话式生图拿不到模型名与seed,分辨率与时间文件属性自带);立规处remote-gen-prompts头注改写(标题复现性→出处备查),转抄两处同步(art-assets-checklist §3.5/art-prompts §5流程注),手绘件登记项(手绘者/日期/源档尺寸)并入同一轻量口径(作者有则记);art-prompts版本号v2.10→v2.11;待办清单条目同批登记并删除(net-zero) 2026-10-30 04:52:50 +08:00
DKChan 3607271575 注销(待办): 在途草稿整理批(3349 失管草稿)整条删除——审计 P2-1 口径错配:ui-reuse-2026-09-23 系下载的第三方 CC0/OFL 素材包(占 3043/91%),vN 命名率与冗余量法对第三方包不适用,实测重复文件仅浪费 0.1MB;artwork/ 与 archive/ 均 gitignored,不进提交/构建/交付,不构成维护成本;用户裁不处理。留档约束:该包不可整删——quantize_ui_skin/render_sample_sheet/render_sample_sheet_v3/render_level_map_sheet 四个工具脚本按路径引用其中的字体/面板/图标/石纹 2026-10-30 04:30:19 +08:00
DKChan 9154f03b48 收尾(场景探针闸合并批): 待办条目完成整条删除——03/04并「03 场景探针」一行,窄试车十段exit0 2m22s,worktree全量闸all_green=True 3m25s,基线两键随批换代(b1e0f29f);引用面三处同步(README/process-conventions/iter技能) 2026-10-30 04:29:01 +08:00
DKChan b1e0f29fe3 合回(工程批·场景探针闸合并·基线换代): baseline.json两旧键(03探针--click/04探针--full)换「03 场景探针」green键;worktree全量闸复跑all_green=True(15闸全0,03场景探针真跑十件)——批本体55a8f270,验证3m25s 2026-10-30 04:28:39 +08:00
DKChan cfb528b917 工程(场景探针闸合并·基线换代): baseline.json删「03 探针 --click」「04 探针 --full」两旧键、预置「03 场景探针」green键(实测真值=本worktree全量闸03场景探针exit0十件全跑);02/05/09存量红键未动——归「闸基线两键换代」在册条目 2026-10-30 04:20:46 +08:00
DKChan 55a8f27026 工程(场景探针闸合并): 03/04两闸并一行「03 场景探针」——gate调用probe_gate.sh --click --full一次跑齐十件(公共五件只跑一遍,窄试车2m22s十段exit0),04号让空不重排;影响面SKIP块与头注/影响面注释同步;README闸03/04提法与process-conventions影响面句换代;probe_gate.sh零改动(--click/--full保留为开发期单跑开关) 2026-10-30 04:08:48 +08:00
DKChan f9fa8d85fa 登记(工程批): 场景探针闸合并——03/04两闸并一行+闸名换代(probe_gate.sh零改动,公共五件去重复跑) 2026-10-30 04:05:02 +08:00
DKChan c8f5091d0f 收尾(迭代43批后收尾): 反哺点13条处置闭合——落文四处(art-bible v3.23 §1.4样张登记句与§6.1素底牌实装事实/art-prompts v2.10 §5产出状态/战斗表现 v1.2五项过闸状态/test-conventions粒度句撤销措辞按§8.4口径清偿),消解三条(AGENTS红线已换禁变异禁撤销新口径/血条tween缺陷已由迭代41测试债批修复/该条留档备查),移交两条目(批后待裁三点=B区高度270vs252对齐方向·名字行12px档位归属·换色常量退役/批后小件=能力清单补行归写卡段·意图徽标四色墨CHECKS补断言),方向稿目检登记体验验证清单㊶(方向稿气质/改前改后对照/类型微光provisional观感复核并入,㉘节陈旧批指订正)——产出物退役:iter43实现报告/反哺点/验收审计/代拍板追认单与两卡需求卡片(2026-10-28定稿卡+2026-10-16旧v1.4卡)按吸收即死删除;二三关棋盘观感条目陈旧前置句换代(样张目标规格清单已落art-bible v3.22)——账面换代:待办清单样张条目整条删除+roadmap迭代43行已完成(2026-10-30)/卡片报告列退役保留字+当前位换代(迭代位1–43全完成,下一步=迭代44开工前宜裁PARITY查因重锚与待裁三点) 2026-10-30 03:23:05 +08:00
DKChan d3b977480e 合并工程批(parity-rm-20261030): 对拍闸退役批合回——回放确定性对拍闸(原11号条件闸)整闸退役:闸脚本6c段删除/PARITY_TOUCH_FACES改名CROSSCUT_TOUCH_FACES(02横切回落保留)/基线删条目/parity_anchor.py+parity_compare.py+evidence/parity锚三件删除/tests README能力行与工具行删/架构文档闸清单与验收红线清理/待办清单对拍挂账清理(含并行收尾批新登记的查因重锚条目注销)/迭代44在册卡V0验收载体随退役换代七处(用户裁:整局重放对拍代价不可承受,正确性由既有单测/集成/探针/渲染golden承载);两度并master(闸机提速批+迭代42批后收尾)零冲突;全量域闸全绿回执e79c04e8(domain=full,scope=full,changed=[]) 2026-10-30 02:59:54 +08:00
DKChan e79c04e891 工程(对拍闸退役批·连带二): 删除并行收尾批新登记的「PARITY对拍锚漂移查因与重锚」条目——退役批已删锚与工具,条目主体不存在,查因重锚随闸退役一并注销 2026-10-30 02:56:22 +08:00
DKChan 05d1988fcd 并master(迭代42批后收尾)——架构v1.26反哺点落文+批末移交小件登记 2026-10-30 02:55:50 +08:00
DKChan 7ec2af581a 收尾(迭代42批后收尾): 反哺点两处分置闭合(反哺点1落文=architecture v1.26 ShopSession行对外暴露格按委托壳拆除后实测直连面重写——直连方法清单16+白盒2与set_shop_pool调用面改述;反哺点2豁免登记被零豁免裁决消解不落,v1.25豁免注销在先)——产出物退役:iter42实现报告/反哺点清单/验收审计与两卡需求卡片按吸收即死删除(卡片死期=实现批合回;报告遗留义务先迁:S13重锚6键离账折入零豁免条目收账句、PARITY对拍锚漂移查因与重锚新立条目、批末小件四条新立条目)——账面换代:待办清单尾波清偿批与商店委托层拆除两条目整条删除+roadmap迭代42行已完成/卡片报告列退役保留字+iter41注脚152死键refresh已执行订正(fafb4fee)+当前位换代(迭代位1–42全完成、迭代43已合回待收尾、下一步=iter43收尾与迭代44开工) 2026-10-30 02:54:15 +08:00
DKChan 6b17d8f307 并master(闸机提速与防作废批合回)——02分片并行+代码域起手自检+worktree素材同步,与对拍退役批同改闸脚本,冲突就地解 2026-10-30 02:46:54 +08:00
DKChan 42c7d879ed 工程(对拍闸退役批·连带): 迭代44在册卡「战斗编排归约批」七处对拍引用随退役换代——V0验收主载体由PARITY对拍改挂关联套件并集(用户裁:其他测试函数保证正确性)、能力行引用换代、§2待裁点⑤注销、§3触面句与§4契约句、§13零触碰声明、§14 L3条款④⑤重排、§16审计凭据句——pipeline_gate 13卡全过 2026-10-30 02:38:57 +08:00
DKChan 8491505929 工程(对拍闸退役批): 删除回放确定性对拍闸及全部配套件——闸脚本6c段整删、PARITY_TOUCH_FACES改名CROSSCUT_TOUCH_FACES(02横切回落判定保留)、回执指纹NEED-APPROVAL分支清理、基线删11号条目、parity_anchor.py/parity_compare.py两件删除、evidence/parity锚删除、tests README能力行与工具行删行、adventure_monte_carlo台账行换代、架构文档闸清单删条件件与验收红线史句清理、待办清单数值窗口删对拍锚重锚句与触面粗粒度挂账行删、settle_segments_test头注去失指句——用户裁决:整局重放对拍代价不可承受,正确性由既有单测/集成/探针/渲染golden承载 2026-10-30 02:28:28 +08:00
DKChan db5abb8527 收尾(闸机提速与防作废批): 待办条目完成整条删除——实测全量闸3分29秒全绿66套件(对照基线约24分);窄试车6态;素材同步实战补拷247件;iter技能合回段三连成文;C批探针渲染并行未立项 2026-10-30 02:23:57 +08:00
DKChan 4ecdcf444f 合回(工程批): 闸机提速与防作废——02分片并行(run_all.gd加--shard/--shard-count,串行道=RNG日志会话六套件钉第1片防同种子日志互踩,空片放行,逐套件日志_s<k>防覆写)+gate_unit多进程(GATE_SHARDS默认4,=1回落原路径,片序合并日志供错误扫描与指纹取材——指纹绿记green红取FAIL排序集,免记账换代)+code域起手自检(熵增预检红灯即止/master推进黄牌/素材自动同步)+tools/sync_worktree_assets.sh(增量拷assets与.import边车,缺件或缺.godot时编辑器导入刷新,相对路径防杂散目录);窄试车6态+全量闸实测3分29秒全绿66套件(基线24分) 2026-10-30 02:23:18 +08:00
DKChan 1ee65d854b 工程(闸机提速批): 02分片并行+代码域起手自检+worktree素材同步——run_all.gd加--shard/--shard-count分片(串行道=RNG日志会话六套件钉第1片防同种子日志互踩;空片放行;逐套件日志_s<k>段防跨片覆写)+gate_unit多进程并行(GATE_SHARDS默认4,=1回落单进程原路径;日志按片序合并供错误扫描与指纹取材——指纹绿闸记green红闸取FAIL行排序集合,不受片序影响)+run_all_gates.sh code域起手自检(熵增预检红灯即止/master推进黄牌/worktree素材自动同步)+tools/sync_worktree_assets.sh(主区→worktree增量拷assets art与.import边车、缺件或缺.godot时编辑器导入刷新;相对路径拷贝防杂散顶层目录) 2026-10-30 02:17:05 +08:00
DKChan 31c801cd5a 登记(工程批): 闸机提速与防作废批——02分片并行+代码域起手自检+worktree素材同步(来源=迭代42合回六轮重跑复盘会话; C批探针渲染并行缓做不立项) 2026-10-30 02:11:47 +08:00
DKChan 65dff22fcf 合并迭代42: gdstyle尾波清偿批+level_runner商店委托层拆除 2026-10-30 01:42:26 +08:00
DKChan 0fc656c6ac iter42-收尾: 实现报告反哺点验收审计落reports+PARITY归因落档(迭代40/41存量) 2026-10-30 01:37:01 +08:00
DKChan 9c12d0a683 iter42-卡2: level_runner委托壳19枚拆除+shop公开化直连键7=33+残留零真残留+基线记账03/04/05(用户裁) 2026-10-30 01:34:06 +08:00
DKChan 66e6d0452a iter42-卡1: gdstyle尾波清偿批——board_view五段拆分与构建器/装板域拆BoardInstaller/wr5改址,键1-4+9+10归绿 2026-10-30 01:33:47 +08:00
DKChan 3b66d1a689 合回(迭代43): 战斗一屏样张批单卡合回——五项实装定值(血条#A8321F/条底收常量/徽标FS_HINT/护甲三枚PNG/名字行素底不压地台/地台件九宫格接装)+样张脚本扩展(两版出口/整屏四项S1–S4/战斗表现五项P1–P5/V1/V10/A5b,机检30/30全绿)+新单元套件standee_present_values(V5–V9十八断言)+theme_projection V4锚换代(§5.5同生死,双半强替代);全量双域闸全绿回执34eba7a3(domain=full,scope=related,16闸15绿+对拍SKIP);独立验收审计通过(三自查跑批原样绿,V1–V10十行全过,削弱零缺漏);批件三份+审计报告落reports,反哺点13条归文档批 2026-10-30 00:23:22 +08:00
DKChan 0c5eb2133d 审计(迭代43): 独立验收审计通过可合回——审计报告落reports(三自查跑批原样全绿:样张30/30、单测18/18、theme_projection164/164;V1–V10十行全过;削弱检查零缺漏,V4锚换代证实双半强替代);实现报告§7结论落位+V6行号笔误订正;反哺点13补意图徽标四色墨机检断言(审计附注,复算4.97–5.62全过) 2026-10-30 00:22:31 +08:00
DKChan 34eba7a332 iter43-卡1:战斗一屏样张批:五项实装定值落码——血条填充#A8321F经UiKit.HP_BAR_FILL(§4.2语义物件,#689CE8退役值零残留)/条底收HP_BAR_TRACK/顺位徽标提档FS_HINT/护甲三键换24×24 PNG(UiKit.TEX_ARMOR_*单点,缺位回退字形)/名字行垫HP_BAR_BACK素底牌且基线上移地台顶上方(§6.1/§7.2,几何单点_name_plate_rect+name_line_probe扩出口)/地台件TEX_STAGE_PLAZA九宫格横向拉伸接装(缺位回退程序化三段)——样张脚本扩展:draw_base两版画布出口(1280×800基座+1920×1080全局等比1.35×左右黑边96px)/B区按代码252出样/mockup镜像五项定值/S3b色板变体第二套/S5三组关键帧条/S6剪影三语境+描边对照+遮挡对照组/整屏四项断言S1–S4/战斗表现五项P1–P5/A7护甲对比锚/V1两版尺寸/V10基线并排/A5b成稿净空(机检30/30全绿)+--gen-assets四件素材产出入包;新单元套件standee_present_values(V5–V9真实入口断言18条全绿)登记manifest;三枚护甲PNG入包;批件三份落reports(实现报告/反哺点12条/追认单11条);并master合入态订正(迭代41测试债批清偿存量红与tween缺陷) 2026-10-30 00:00:01 +08:00
DKChan d9e5c019fd 收尾(迭代 41 测试债批): 待办条目「迭代 41 测试债修复」完成整条删除+收尾实现报告落 reports/(登记范围完成度+实测扩出的四处迭代 41 特许合回遗留产品回归:数字色牌守卫/open_victory opts 改型/站桩补间目标/调参模拟运行态收尾,含 golden 2.74%→0.00% 与墨族自适应读数证据+闸回执读数+实机目检三项);新立条目「闸基线 02/09 两键换代」承接已归绿存量红指纹的重生成(代码面,随下一代码批落) 2026-10-29 23:07:39 +08:00
DKChan 43906cdd8c 合回(迭代 41 测试债批): 代码域闸全绿回执 afd1a41f(域=code、scope=full、02/09 基线红→现绿、零新增红)——9 套件+S9/S10/S11 归零+死常量 SCHEMA_VERSION 删除+iter41 特许合回遗留的四处产品回归修复(数字色牌守卫/open_victory opts/standee 补间目标/调参模拟运行态收尾)+探针与测试面随拆件改址 2026-10-29 23:03:57 +08:00
DKChan afd1a41feb 修复(迭代 41 测试债批·02 收口): config_db_test meta 行族改直读表成员(_meta_rows 访问器随 iter41 退役)——02 单元与集成全套件归绿 2026-10-29 22:54:37 +08:00
DKChan 413124b0b8 修复(迭代 41 测试债批·闸面续修): 03/04/05 三闸归绿——①board_cell_unit 数字色牌守卫恢复(iter41 卡2 分层拆分丢 if plate: 致 dungeon 数字一律垫牌,L1 棋盘 golden 由 2.74% 回 0.00% 逐像素复现基线);②curio_interact_controller.open_victory 第 4 参随 opts 改型(字符串→{"gp_summary":…},胜利总结面续玩恢复路径运行期断链);③探针随 iter41 拆件改址(ui_input_probe 格面成员入 st 六处、ui_struct_probe level_screen._runner→._map._runner);④config_db_test 商店行族改直读表成员(_shop_rows 访问器已退役) 2026-10-29 22:45:09 +08:00
DKChan 39c1fc69fb chore(todo): format 2026-10-29 22:29:36 +08:00
DKChan 833d483ef8 编排(迭代44/45): 零豁免五键四卡+补件批按用户裁编法编入——迭代44=组合根注册面批+战斗编排归约批+测速循环批(中值合计≈7.875个工作日,卡序A/B/D),迭代45=呈现层状态件批+gdstyle尾波补件批(≈7个工作日,卡序C先/补件后-board_hover半边硬前置);编排四查过(五卡在盘/凭据齐=architecture v1.25+设计文档+grilling复核过/估时常规区间/闸PASS交叉核7链);战斗域结构深拆批条目状态累积已写卡+已编迭代44两段(专指宿主瘦身子项,其余两子项仍待文档设计);编入不等于开工,实现段由用户/iter发令 2026-10-29 21:58:36 +08:00
DKChan 9855bf0a10 复核(grilling五卡)+修正: 复核报告落盘——五卡全部有条件成立/零回炉,file:line抽验约90处命中,键账成员迁移账全平,与五项用户裁决零冲突;blocker1项修正(卡A与iter42文件交叠失实声明改为如实+开工前置=iter42合回);应修9项全落——卡A/B互写ui_input_probe共享触面禁并行,卡一board_hover半边硬前置升格+resume_adventure行号订正:141+V1判据面改型后口径+引用束计数实数,卡一表D补_flush_row_queue转发壳归宿,卡二对照表行2/11静态判据移域口径申报(行2改回归主证/行11改全量闸半边),卡三算术订正(删24项=37-22-2+2新=15账平),卡五测量面专属能力行「调参测速批量模拟」当场入表并改锚三行(废悬置) 2026-10-29 21:06:12 +08:00
DKChan 755dd8232c 写卡(零豁免五键·后两卡): 《呈现层状态件批》v1.0(单格两层域件+站桩两件并一卡,30→10/19→10真实25成员纠偏,潜伏红T0定谳随批修复,估时3-5天)+《测速循环批》v1.0(process_frame信号步进状态机,申报行为改动=收尾置假还原,双实现对拍入批,估时1-1.5天)——均零待裁点开工门U0;五键批条目翻已写卡(四卡路径逐行入实况,战斗编排归约批记账归战斗域结构深拆批条目);测量面能力行锚定按库内先例申报,专属行入表列写卡段后续 2026-10-29 20:30:37 +08:00
DKChan 672407dd5a 写卡(零豁免五键·前两卡): 《组合根注册面批》v1.0(main 49→15,ui_registry+三域容器,867改址,估时3-4天)+《战斗编排归约批》v1.0(RoundCycle/BattleFacts,38→15,估时2.5-3.75天,编队前置=iter42合回)——均零待裁点开工门U0;卡B接收方=在册条目「战斗域结构深拆批」宿主瘦身子项(实况补承接更新:设计闭合v1.25轴丁+豁免第三条注销,其余两子项仍待文档设计);卡B静态键账判据按test-conventions §8.4出口移散文申报 2026-10-29 20:05:45 +08:00
DKChan 183f4da3a4 设计闭合(零豁免五键+补件批): architecture v1.25落文——S13三条豁免注销(main/battle_orchestrator/tune_sim),七新件登记行(ui_registry/board_hover/RoundCycle/BattleFacts/cell两层域件/standee两件/breakpoint_store),轴丁两件设计闭合,attune改判,gdstyle inner-class计数截断缺陷登记;用户再裁恢复D2/D4拆件并按D5留债写入尾波条目(批末豁免登记步骤不执行);新卡《gdstyle尾波补件批》v1.0(零待裁点,估时2.5-3.5天,待iter42合回编入);待办清单新增补件批与零豁免五键两条目;roadmap迭代42状态按用户口述订正为进行中 2026-10-29 19:49:43 +08:00
DKChan ee4efad54e rules(行为提醒精简): AGENTS.md「主动检索提醒」节改一句话版(用户裁定)——「训练记忆缺失或可能过时的问题,先搜最新主流的答案再回答。」触发判据由轨迹数据导出:四类实例(引擎 4.7 迁移指南核实/gdUnit4 选型/Godot 4.7 最佳实践判据轨/art-bible 参考池误判勘误)共同点=答案在仓库外且训练记忆缺失或过时,故弃「不确定」自判与题材枚举两版草案;首版四条细则随精简删除——引擎指针已在 Engine Version Reference 节、外查证据纪律各红线已覆盖,均属重复 2026-10-29 07:42:19 +08:00
DKChan e8c6f9e572 编排(迭代43): 战斗一屏样张批单卡编入——中值≈6.5个工作日(区间5.5–7.5)处5–10常规区间,状态待开工;编排四查过(卡片在盘=合回提交cc0627ba入库版/美术线凭据齐=实况2026-10-28美术设计段闭合+art-bible v3.22条款版本在关联文档清单/估时常规档/流水线闸PASS总表43行交叉核3链);零待裁点开工门U0;用户2026-10-29点名编入迭代43 2026-10-29 07:25:12 +08:00
DKChan 517412c7e9 登记(零豁免五键设计): 五份设计方案落盘——组合根注册面(main 49→15,一门面+三域容器,867探针改址)/战斗编排归约(38→15,RoundCycle+BattleFacts)/单格状态件(30→10,两层域件)/站桩状态件(19→10,vitals+motion)/测速去await(process_frame信号步进状态机);两代理独立实证gdstyle inner-class计数截断缺陷(基线登记值欠估);两处疑似潜伏红待T0核实(ui_input_probe inner成员直读/tune_sim收尾置假与wd8矛盾);待裁点21条呈主会话汇总 2026-10-29 07:22:50 +08:00
DKChan 33d6a2a6e9 rules(行为提醒): AGENTS.md 新增「主动检索提醒」软提醒节(非硬性红线)——版本敏感/时效敏感知识(引擎 API/设计模式/架构实践/行业惯例/工具链)或自判没把握处,即使训练记忆有现成答案也先 WebSearch/WebFetch 查证再下结论;来源取官方与主流现行实践、日期版本须匹配项目环境、过期内容不采信;外查结论只作呈报与待裁点证据,要进文档走 doc-design、要进代码走五段流水线,不越数值/架构单源,库内事实查规范导航不外查;引擎 API 专项规则维持 engine-reference 不变,本节为其通用版——源起=用户 2026-10-29 裁:engine-reference 旧规则触达不可靠且只覆盖 API,需常载入面的通用提醒;登记与完成同会话坍缩条目免落 2026-10-29 07:22:18 +08:00
DKChan 14a10e5e74 编排(迭代42): gdstyle尾波清偿批+level_runner商店委托层拆除两卡编入——中值合计≈5.375个工作日(3.625+1.75)处5–10常规区间,状态待开工;编排四查过(卡片在盘/凭据齐:gdstyle设计段裁决D1–D6 2026-10-29用户裁闭合+architecture v1.24轴丙BoardInstaller落文,level_runner第〇步2026-10-29放行零待裁点;估时常规档;流水线闸PASS交叉核2链);卡序=gdstyle先(T0十一键对账按基线原值全数对上,键7终值33由后段并排卡兑现——D6裁定两卡同迭代并排编队先后皆可、交叠区段不相邻零冲突);战斗一屏样张批(已写卡在册)经用户2026-10-29裁不编入本迭代留待后续编排。附记:按文件名暂存连带带入并行会话(架构审查批)当时未提交的待办清单登记条目「架构审查优化讨论——全库架构审计读数的逐条裁决与整改分批」(状态待文档设计)约10行——属登记处活文档合法内容、闸45条目全过,登记沿革自本提交起算,其审计报告件仍归该会话自提 2026-10-29 06:33:24 +08:00
DKChan cc0627ba6d 美术(战斗一屏样张批·合回): 样张需求卡片 + art-bible v3.22 定值(第二道门口径/血条危险红/素底深档/护甲三档/地台独立件/三关色板终值程序) + 三关色相互辨抽测方案设计闭合;冲突解决取格式层 master 整备成果 + 定值层本批成果,合回前补 req-card §8 判据绑定面列与整备批退役词清理 2026-10-29 06:28:50 +08:00
DKChan 76425feb6e 美术(战斗一屏样张批·合回前闸红修复): 收口前补三处 master 换代/整备批后新暴露的合规红——①卡片验收对照表补「判据绑定面」列(req-card §8 判据口径换代硬性列,逐行填可观测提交面);②art-prompts §5.4/§5.5 清 R3 正文修订注记与「随裁定作废」注记(440/516/531);③art-bible 第二道门口径退役词「不得」→「禁止」三处(2026-10-15 退役);④art-assets-checklist §3.7/§4 清本轮新增的日期批名注记(不把脚印留进 master 存量债)。文档域闸验收通过无新增红 2026-10-29 06:28:26 +08:00
DKChan b314269b05 Merge branch 'master' into art-splash-doc-20261028
# Conflicts:
#	design/art/art-assets-checklist.md
#	design/art/art-bible.md
#	design/art/art-prompts.md
#	design/art/asset-path-map.md
#	design/art/character-design.md
#	待办清单.md
2026-10-29 06:22:36 +08:00
DKChan 215da31591 修复(迭代 41 测试债批): 9 套件归绿+静态扫描 S9/S10/S11 清零+删死常量 SCHEMA_VERSION——两个产品回归随批修(standee 补间目标改子对象 st/tune_sim 运行态收尾归还 run),测试面按 iter41 拆件改址(slots/meta_state/crossrun/_forge/_side);S9 类型闸 24 处(node_map 三处给 Array[int] 真类型,其余动态边界 type-hint: ignore 逐处注记)+S10 头注日期 4 处+S11 退役词 1 处;config_db 头注「--script 无 autoload」订正为实测事实(autoload 并存、共享 .tres resolver 归最后装载者) 2026-10-29 06:04:40 +08:00
DKChan 6dea23729b 收尾(合回闸关联档+闸机编排快速失败批): 待办清单两条目完成整条删除的收尾报告落 reports/——改动文件清单、关联档选闸算法说明(三件保险恒跑/02 申报∪推导并集/横切回落全量/回执 scope 字段)、快速失败行为、孤立验证证据(沙箱七场景+闸源选跑 9/65+2s 连击中止)、闸机证据与存量债记账(9 套件+S 域红=迭代 41 在册债非本批新增;指纹 pid 归一化;基线 02/09 实测重生成凭据=用户 2026-10-29 带红合并裁;master 推进作废重做经过;最终回执 tree 与 master^{tree} 校验一致)——报告接收方=已删除的两条待办条目,随批入库 2026-10-29 05:28:30 +08:00
DKChan 8bb3541c5c 合并分支 gaterel-20261029(合回闸关联档+闸机编排快速失败)入 master:①收口跑批按改动关联面选测——00 冒烟/01 系统测试/09 静态扫描三件保险恒跑,02 单元与集成改「选套件跑」(卡片申报∪diff 推导并集,横切回落全量),回执加 scope 字段;②run_all 连续 3 套件超时快速失败中止整轮;③AGENTS/process-conventions §3/test-conventions §4/code-conventions §3/game tests README 规则同批同步;④待办清单两条目销条;⑤收口修正:红项指纹 pid 归一化+基线 02/09 存量红按实测重生成(凭据=待办清单迭代 41 测试债修复条 2026-10-29 用户裁带红合并承接);已并入 master 60c1a962 后重跑全量刷新回执;收口回执 game/tests/.gates/receipt.json(域=full、scope=full、存量红零新增全绿,tree=分支尖内容树) 2026-10-29 05:26:15 +08:00
DKChan 0f9d1c857b 并入 master(60c1a962 尾波清偿批登记)——合回前刷新回执前置步骤 2026-10-29 05:16:24 +08:00
DKChan 80b49c0f96 工程(合回闸关联档批·基线记账): 存量基线 02/09 两键按实测重生成——02 记存量红 fp f9865f1a5a44(9 套件失败,逐项对上待办清单「迭代 41 测试债修复」条)、09 改记现行红项指纹 1a9ad773489f(S9 24 处+S10 4 处+S11 1 处;旧指纹 8467b4588a20 所记 gdstyle 存量红已随迭代 41 还清过期);人工确认凭据=该条目 2026-10-29 用户裁带红合并(代码域现存红全部由该条目承接);基线滞后成因=迭代 41 两卡特许直接合回未重跑代码域闸。记账依据=本批 worktree 收口全量闸实测(commit 117a37e2 域=full,本批改动零新增红);此账使后续跑批按「存量红·对基线零新增」判定,修复批实测归绿后再换代 2026-10-29 05:05:45 +08:00
DKChan 60c1a962ab 登记(尾波清偿批·v2.0定稿): 设计段裁决闭合D1-D6——D1装板域拆新件BoardInstaller两修正(复位块留宿主薄壳/_board_generator随件退役,architecture v1.24轴丙与功能层行落文)·D2悬停域不拆·D4断点域不拆·D5五降值键豁免终局·D6写卡期修正改判并排编队(原吸收案否决:落笔发现该域已有定稿卡v1.1零待裁点且契约精度更高,销之重写属损耗;两卡同迭代各执各合同,level_runner交叠为不相邻区段);卡v2.0删T6/V6,估时3–4.25工作日,归绿6豁免5,零待裁点可编排;待办条目状态=文档设计已闭合+已写卡 2026-10-29 05:05:29 +08:00
DKChan b0e3353103 美术(三关色相互辨抽测·美术设计段闭合): 抽测三件缺口定稿——①取样面=3关×5组15对×3格+9基准=54格,画布640×1120,正图段+灰度副本段,装饰变体底带而迷雾记忆态底不带(§4.2/§4.6.3/§6.5 三者永不同格);②判据=目检四条+机检四条并用(沿 §1.4 目检先行机检冻结),机检阈值全取本作在役值(合成对比≥1.20/格底-微光三通道差不得全部<8/样图读数与合成模型差≤±2/255/铺底准入 L≥0.30 饱和≤0.55 避300-345),色相通道刻意不设机检阈值(圣经无条文,自造等于把观感问题伪装成可机检);③产物形态=并列样图+逐对读数表兼有,故改动面补「美术素材」与「工具代码」两档。新增色值 0 枚——tile_closed/wall_ambient 属 §4.1 分层②场景环境子板不占 48 额度,终值从已在役的三关瓦片色表墙顶受光档槽取。同批改待办清单:条目状态推进为美术设计已闭合+待执行,按状态机删除已闭合条目的缺口属性(三件定值已并入闭合段),改动面改写为文档+美术素材+工具代码,记设计轮闭合凭据与方案路径 2026-10-29 04:59:12 +08:00
DKChan 117a37e2a5 工程(合回闸关联档批·收口修正): 红项指纹归一化超时挂死行的 pid——fingerprint 只取 FAIL 关键行哈希,注释自陈「忽略时序/耗时噪声」,但超时行内嵌 pid=NNNN 逐轮变化使存量红指纹无法稳定登记(首跑实测:02 的 forge_workbench 超时行带 pid 使指纹不可复现);归一化 pid=N 后再入哈希,判定口径(FAIL 行集合)不变。首跑另测得:02 现红 9 套件+09 红项清单变化均系 master 在册存量债(待办清单「迭代 41 测试债修复」条 2026-10-29 用户裁带红合并承接:config_db 解析错/ooob 退出码 2/table_structure_guard/treasure_items/standee_fx/forge_workbench 超时/tree_requires_level/save_manager_domain/theme_projection+S9 24 处+S10 4 处+S11 adventure_table:55),非本批新增——基线滞后的成因=迭代 41 特许直接合回未重跑代码域闸,处置见下一笔基线记账提交 2026-10-29 04:51:29 +08:00
DKChan cf0e40cca8 复核(level_runner 商店委托层拆除): 卡片 v1.1 grilling 复核门通过——跨类同名扫描判类归属(BattleOrchestrator.player_request_refresh/CurioDeck.pool_stock 排除面)、_shop 词级归零判据、set_shop_pool 陈旧调用方陈述订正(构造 ctx 供给,产品码零调用方)、测试面精确计数(27 命中=25 实改+2 同名);零待裁点放行 2026-10-29 04:49:34 +08:00
DKChan c4862e7d04 写卡(level_runner 商店委托层拆除): 需求卡片 v1.0——19 委托壳拆除+调用面直连 ShopSession 零行为改动;能力清单 GP 钱包行入口列随改址形态换代;待办条目推进已写卡 2026-10-29 04:40:00 +08:00
DKChan 9f94c9e100 工程(合回闸关联档+闸机编排快速失败): 收口跑批按改动关联面选测+红树快速失败——①tools/run_all_gates.sh 关联档选闸(推广 2026-10-28 影响面选闸):00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降(测量面批不再免 00);03/04 探针与 05 渲染闸维持影响面口径;02 单元与集成改「选套件跑」——套件集=需求卡片申报∪diff 推导并集(申报=本批 diff 触及卡片∪roadmap 进行中行登记卡片的对照表「验收测试」列测试文件名;推导=脚本内套件族映射表按 game/ 代码面文件归族,映射不到一律横切),横切(公共件前缀 config_db*/rng_service*/save_manager*/meta_progression*/main*、project.godot autoload 注册面、game/data/tables、PARITY 触面表)→ 02 回落全部套件;PARITY 触面表上移单点定义(gate 11 与横切判定共用同一清单);回执加 scope(full|related)与 scope_terms 字段,worktree-guard 校验逻辑不动——②game/tests/run_all.gd 快速失败:连续 3 个套件超时(可调常量 CONSECUTIVE_TIMEOUT_ABORT 初值 3)即中止整轮、汇总已见失败与超时清单、退出码非 0,不连续超时照旧逐个回收;_run_suite 改返 {fail, timed_out};新增 --only-source=gates 来源标记(关联档选跑横幅,区别于开发期选跑免责句),无选跑参数时行为与现状完全一致——③规则同批同步(禁只改闸不改规则):AGENTS.md「交付前验收」句与硬性红线改按关联档选测口径(所选域绿即可合回,全量回归由用户自发、合入后无自动兜底,2026-10-29 用户裁)+规范导航行同步;process-conventions §3 第 8 条合回闸细则同口径;test-conventions §4 改「提交前验证」、code-conventions §3 主题行随改;game/tests/README.md 工具速查 run_all.gd 行(快速失败+闸机关联档选跑+manifest 实数 65)与挂死防护注、门禁/本地闸回执字段句同步——④待办清单「合回闸关联档」「闸机编排快速失败」两条目完成整条删除。孤立验证:推导沙箱七场景(行为面归族/触面横切/测量面批/main* 横切/project.godot 横切/申报并集/横切优先)+run_all 闸源选跑 9/65 绿+2s 截止连续超时第 3 个中止(exit 1 带汇总) 2026-10-29 04:36:26 +08:00
DKChan bf63a77fa9 美术(战斗一屏样张批·写卡段): 重新立需求卡片(旧卡 v1.4 已于 2026-10-17 裁定非有效卡)——十二节齐备,含样张两版产出、整屏四闸与战斗表现五项断言、五项代码定值落码(血条换 #A8321F/站桩名字行垫 HP_BAR_BACK 素底且不压地台/顺位徽标提档 FS_HINT/护甲图标换三枚 24×24 PNG/地台件独立引用)、程序化产出两件入包、改前改后并排对拍图;现状事实代码级实查 file:line(血条填充裸字面量 standee_body.gd:444、徽标裸 13px :437-438、名字行基线 box.y-13 与地台 box.y-18..box.y-6 重叠、B 区代码 252 对设计文 270、立绘与瓦片不入 git 靠 asset_root 两级回退取用、站桩实装仍字形回退);设计契约立十条含 2026-10-28 新验证口径(禁变异/撤销取证,须真实入口真实跑批通过)与「样张不判定立绘精细度」。同批改待办清单:样张条目状态推进为已写卡、卡片路径记入实况关联文档清单,修两处死指(引用已按吸收即死删除的过程稿)与两处过期版本号(v3.21→v3.22) 2026-10-29 04:34:09 +08:00
DKChan 04127485f6 登记(尾波清偿批): 需求卡片v1.0草稿落盘——S13基线重锚后11键定向清偿(board_view 6/level_runner 3/battle_orchestrator文件长/adventure_runner类变量),归绿6降值5不承诺清零;四拆件项(装板域/悬停域/商店域/断点域)备选案并列待设计段裁决D1-D6,编入迭代前置=待裁点清零;发现ShopSession件已在位(迭代37建),商店域=删委托壳,与在册条目「商店委托层拆除」同面(D6并案/剔除待裁);能力清单同批入「棋盘悬停反馈」行(写卡同批义务,流水线闸能力行可核);待办清单新增条目(状态=文档设计中+待写卡) 2026-10-29 04:33:33 +08:00
DKChan 6abaf67dd5 合并分支 caplist-20261029(能力清单改版——验证账本改实现指导地图)入 master:能力清单四列制义务声明定位(写卡当场入表+实现照单落名+收尾核对,废滞后一个迭代)+test-conventions §8.3/§8.9 改写+capability_audit 双通道判据+pipeline_gate 新增行收紧;收口回执 game/tests/.gates/receipt.json(域=docs 全绿,tree=分支尖内容树) 2026-10-29 04:06:03 +08:00
DKChan b96aab8a5a 合并 master(6690c2a7 文档纯度批)入 caplist-20261029——capability_audit.py 冲突解法=保留 master 反引号调用形态判红(CALL_FORM_RE)与本批四列双通道判据;docstring A 条两侧合写 2026-10-29 04:05:25 +08:00
DKChan 21d40a5388 收尾(能力清单改版批): 待办清单「能力清单改版——验证账本改实现指导地图」条完成即整条删除+改动报告落 reports/(改动面、判据变更前后对照、真实入口双通道延伸申报、闸机验证与回执证据)——报告接收方=该条目,随批入库 2026-10-29 04:04:08 +08:00
DKChan 2a087c9ead 工程(能力清单改版——验证账本改实现指导地图): 能力清单定位改实现指导+阅读导航+义务声明,只宣布「要验证什么」不记验证状态(落地账归改动—验收对照表+合回前独立审计)——game/tests/README.md 八张能力表瘦身四列(能力|系统|真实入口|代表用例,删「最后验证批次」「缺口」两列,存量行内容保留不回溯验证)、引区块改版(义务清单定位+写卡当场入表+实现照单落名+收尾核对,废「滞后一个迭代」),覆盖域表保留现状不动;test-conventions §8.3 维护锚整段改写、§8.9 三层模型中层「覆盖索引」改「义务清单」并同步法则与机器闸句;tools/capability_audit.py 判据随改(CAP_COLS 收窄四列含系统列非空、删「最后验证批次=真实 commit」检查、代表用例与真实入口改双通道=代码在或卡在 reports/需求卡片*.md——两头都无=红、条件闸行豁免保留代表用例口径);tools/pipeline_gate.py 对照表能力行收紧(「新增:<能力名>」同样要求该能力行已在清单在场——写卡与入表同 commit)。新判据经造红探针孤立验证(两头无=红/卡在=绿/代码在=绿;新增不在表=红) 2026-10-29 04:02:20 +08:00
DKChan 6690c2a75b 文档(实现指证清理): GDD 全树纯代码指证换代中文设计表述+纯度闸新增反引号调用形态判红——六类清算(方法/信号/类/常量/探针/调试与遥测入口名/提交号与运行时文件路径),保留面=数据索引(.tres 表路径与表名、表字段、表内枚值、存档键、封闭词表标签)并在 doc-conventions §5 明文写死;能力审计闸加 CALL_FORM_RE 窄规则兜底;顺修 §5 表两行误合并 2026-10-29 03:58:14 +08:00
DKChan fafb4fee0a 工程(gdstyle基线死键清账): 基线按实测重锚168→16键——152死键随迭代39–41还债消失清账,3条活键只降不升重锚(battle_orchestrator文件长1037→1035、board_cell_unit类变量31→30、standee_body类变量29→19),16键与迭代41键账终局构成逐键吻合,S13闸转绿(126文件16命中基线内零新增零恶化);待办清单条目完成即删 2026-10-29 03:43:19 +08:00
DKChan 4a7565592e 订正(表结构版本): 数据配置删「当前 = 2」版本行与配套验收/依赖表述——用户裁甲案,存档版本为唯一现役版本常量
- 数据配置.md:删调参表「表结构版本」行、验收标准「版本常量在位」条,依赖表与不负责表去掉「版本常量」表述(19 张表与 schema 均无版本字段,原表述三处失实)
- 存档序列化.md:迁移链「版本号推进仅为表结构版本对齐」改「仅随表结构变化」,消掉已退役概念
- architecture.md:save_system 契约锚与 RNG 快照节两处 SCHEMA_VERSION 改现役 SAVE_FORMAT_VERSION
- 待办清单:迭代 41 测试债条追加死常量 SCHEMA_VERSION(全仓零引用)随该批删除的登记
2026-10-29 03:31:36 +08:00
DKChan 0c33c3df12 登记(会话三裁决): 待办清单新增三条——①合回闸关联档: 收口跑批按「卡片申报∪diff推导」选关联测试族,三件保险恒跑,横切面回落全量,回执加scope,合入后全量兜底待裁;②闸机编排快速失败: 连续N套件超时中止整轮,红树不再磨2h;③能力清单改版: 验证账本改实现指导地图,四列制删批次/缺口列,废滞后一个迭代,写卡时本批能力行入表且测试名卡里定死,实现照单落名;三条均待用户发令,清单改版试点=下次写卡 2026-10-29 02:58:56 +08:00
DKChan 3dbbbf43bc 登记(验证与清单体系·阶段1收口): 五批落地凭据入实况——03 能力审计接闸上电(915222fa)、05 GDD 清理 124 处实现指证归零;迭代41测试债条目标明带红合并裁(代码域红由该条承接,本批零新增) 2026-10-29 02:55:52 +08:00
DKChan af853b62d5 登记(迭代41测试债修复·待执行): 9 个单元套件失败+静态扫描 S9/S10/S11 三项红——能力审计上电批全量复跑首次暴露,来源=迭代 41 测试特许移交;实况带逐套件证据与红项清单,改动面=游戏代码,只修测试与注解不改行为 2026-10-29 02:55:07 +08:00
DKChan 915222fa0a 工程(能力审计闸上电): tools/capability_audit.py 接入文档域 08e——GDD 纯度+能力清单与覆盖域表结构由机检兜底;baseline 加 08e 绿记录、07/08d 条目随批 5 文档清理实测归绿;test-conventions §8.9 与 doc-conventions §5 补闸件指路句(闸件清单唯一出处=run_all_gates.sh 头部) 2026-10-29 02:55:06 +08:00
DKChan a10bf92656 收尾(迭代41批后收尾): 反哺点落文对账闭合(A1–A6=提交 3213722f;B7/B8/B9 随并行文档纯度批 00312e12 入库——B7 引用块整删承载、B8/B9 三行随批;C10 核对结论=两豁免段在位/模块归属表零行变化零落文动作)——产出物退役:iter41 实现报告/反哺点清单与两卡需求卡片按吸收即死删除(卡片死期=实现批合回、报告遗留义务先迁:四项实机目检入体验验证清单 ㊵、golden 改前锚未建让步入 golden README 备案、152 死键 refresh 换入待办清单新条目)——账面换代:待办清单 gdstyle 存量债条目整条删除(键账终局 16 在册构成随新条目留档)+roadmap 迭代 41 行已完成/卡片与报告列退役保留字+收口注脚(测试特许移交+直接合回)+当前位换代(迭代位 1–41 全完成、下一步=待编池+refresh 可裁事项) 2026-10-29 02:16:50 +08:00
DKChan 3213722fed docs(迭代41反哺落文): architecture v1.23——全清定稿注记转现状登记(ConfigDB 三域容器 7/7/5 与公开面 8、SlotStore 10/4 经 save.slots 直连、MetaStateStore 写口13+读投影6 经 save.meta_state 直连、meta_progression 公开面 20 定稿三并组)+minesweeper_rules 消费依赖补 last_round_end 方法形态注记;B7/B8/B9 三条已随并行文档纯度批 00312e12 入库(B7 引用块整删承载、B8/B9 调参表落点改段路径三行随批入库) 2026-10-29 02:13:17 +08:00
DKChan 00312e12f4 清理(GDD 实现内容·批5): 30 篇 GDD 去 124 处代码指证——能力审计纯度红项归零
- `.gd`/`.py`/`.sh` 路径(含 data/schemas 脚本路径)、测试与探针名、代码行号、提交号全部清出 design/gdd/;唯一保留=game/data/tables/*.tres 表索引(用户裁:GDD 禁实现内容,仅配置索引例外)
- 锻造工坊:删「现役代码事实」「实装边界」两节——实装已随迭代 23 收官、设计事实正文已有(默认值所有权/敌方攻击轴未开/存档域/满级不实装),无独有事实需迁 architecture;正文去指证并清交叉引用
- UX 三份 spec 的「> 事实源=代码路径」体例统一改指设计件;battle/level-map/interaction-patterns 三张组件表的「现有 demo 组件/现有实现锚/状态」列改「载体」,只留状态与设计载体名
- 术语表与 08-Alpha 的事实源列去闸件路径,改指 process-conventions 运行与验证节;08 v1.19→v1.20 与 02 根索引指针随动
- 事实订正三处:存档调参表版本 6→7、数据模型样本 version 6→7(v7 锻造域已落地);冒险结构调参表冒险表字段名按现行表结构对齐(value_segment/quota_segment);删除「存档版本=表结构版本常量同源」这一错误断言(实为两个独立常量)
- 架构文档的未提交改动(迭代 41 反哺落文)非本批产物,未纳入本提交
2026-10-29 02:07:56 +08:00
DKChan f32634446e 工程(流水线闸): 验收对照表加「能力行」列——2026-10-29 起生效,既有名回核 game/tests/README.md 能力表
check_ledger 增第七列:每行写 既有:<能力名> 或 新增:<能力名>(分号可多段)。既有名须在能力表首列可查,新增放行(新能力行由写卡段落表,test-conventions §8.9 滞后一迭代)。日期门控对齐 BINDING_RULE_DATE 同构:文件名日期早于 2026-10-29 的旧卡不回头改。自测四况通过(既有命中/新增放行/既有不存在红/格式错红/缺列红/旧卡免检)。req-card 与 iter 两技能本地件同批同步(不进 git):对照表改七格、写卡段增删能力行并刷最后验证批次、实现批禁写 README。
2026-10-29 01:55:59 +08:00
DKChan ae3c99e34e 工程(能力审计工具): capability_audit.py 入库——七组机检覆盖三层模型的机检半边,本批不接闸
检查组:A 文档纯度(design/gdd 全树禁 .gd/.py/.sh 路径与断言名,围栏块跳过,.tres 表索引放行)/B 系统文档验收标准非空(豁免两件具名附理由)/C 能力行六列结构与格非空/D 代表用例三核(文件存在、在 run_all.gd manifest、func 函数名可达)/E 真实入口方法在产品码有定义(static func 兼容)/F 最后验证批次为真实 commit/G 覆盖域表 25 篇逐篇登记、状态三档、非未覆盖行能力行可回指能力表、部分/未覆盖行缺口指待办清单。自检四道防扫描面空转(文档数/能力行数/套件数/覆盖表行数任一为零 exit 2)。当前实测:能力行 59 全过、覆盖域表 25 行全过,仅余纯度红 124 处(GDD 存量实现细节,归清理批)。接闸与基线登记待清理批之后单独批执行。
2026-10-29 01:54:45 +08:00
DKChan 5377b2ce2e 文档(能力清单v2): 三段扩面+覆盖域表——排雷循环9行/表现与支撑10行/数据配置与契约10行入清单,25 篇系统文档逐篇对照;修两处真实入口笔误与 manifest 计数 64→65
能力清单从核心四域扩到七段(58 能力行+条件闸 1 行),维护规则改写为写卡段维护、实现批禁写(test-conventions §8.9);新增覆盖域表按 25 篇系统文档逐篇对照(已覆盖/部分覆盖/未覆盖),八处缺口指待办清单(新登记「能力清单覆盖缺口补齐」条承接六项,教学与生涯归各自在册条目)。修两处与产品码失配的真实入口(SaveManagerCore.add_keystone/tree_nodes → MetaStateStore/MetaProgression 实有方法);manifest 计数行按实数订正 64→65(62 单元+3 集成),对应待办条目完成即删。pipeline_gate 绿。
2026-10-29 01:54:33 +08:00
DKChan 032d923c7e 文档(验证与清单体系·阶段1): 三层模型入法——验收意图/覆盖索引/执行证据分层,四法则与写卡段维护时序落三份规约+AGENTS.md 红线
test-conventions 新增 §8.9(三层表+四法则=每格非空/每条断言有归属/数量不守恒覆盖守恒/无全局对账总账,含维护时序与文档纯度);§8.3 能力清单维护锚改写为写卡段维护、实现批禁写。doc-conventions §5 增「禁实现内容」行(代码路径/测试文件名/断言名全文禁写,例外=配置文件索引),§6 骨架验收标准口径同步。process-conventions §3 第 11 条与 AGENTS.md 改动四类红线补 `game/tests/README.md` 禁写面。待办条目 5 实况记 2026-10-29 用户三裁与五批落地计划,状态维持文档设计已闭合。pipeline_gate 绿(42 条目/41 行/7 卡)。
2026-10-29 01:48:40 +08:00
DKChan eb9bb53752 合并(迭代41 gdstyle存量债清偿两卡): 卡①核心数据面结构收敛批(85键全清——校验族13文件/生成器族2文件/效果引擎四域拆域+config_db归位三域容器+存档拆件SlotStore/MetaStateStore+meta_progression瘦身20+adventure_table拆段3段件+action_economy删并+spawn签名束化,行为零变化,8新件键账在阈)+卡②呈现层结构收敛批(67清+2豁免在册+2展平改善——视图拆私有族+六状态件+ui_kit公开面26→20+flow context三束+print×3删除;含u14补做与并master并案)——用户2026-10-29裁直接合回 2026-10-29 01:37:25 +08:00
DKChan 2789adf606 iter41-卡2:呈现层结构收敛批:67清+2豁免+2展平改善——拆私有族+六状态件+ui_kit收敛+束收拢+print删;含u14补做、并master并案与批末运行期断链修复 2026-10-29 01:33:51 +08:00
DKChan a613a3cb90 iter41-卡1:核心数据面结构收敛批:85键全清——三族拆域+config_db归位容器+存档拆件+meta瘦身+adventure拆段+spawn束化;行为零变化,8新件在阈 2026-10-29 01:33:50 +08:00
DKChan 10d7c82b6a 美术(战斗一屏样张批·色值裁定落位): 素底明暗档裁深档 #282220——站桩名字行与魔法甲暖白维持不动(12.26:1)、轻甲木色按合并裁定改 WARM_FAINT #CDB88C(8.08:1);额度接受五处换色合并(铲刃→FOG_MEMORY_INK #C4C4C4/提灯→GOLD_DEEP #B07A14/食人魔酋长暖褐皮→暖石 #B8A582/石君王灰皮→STONE #786E62 按次级皮口径/轻甲木色→WARM_FAINT),新增两枚(重甲冷灰 #868C9A 对深素底 4.65:1 对舞台底 3.52:1、卓尔浅紫灰皮 #A79CB8 6.04:1 与 4.57:1,均过图形 3:1),48 色余量 2→0 额度用尽;石君王岩石板甲与重甲冷灰同一枚不另计。art-bible 升 v3.22(动文首版本号与 §4.1:增列两枚并标注过门与语义检查结论、换色合并登记五处、增量纪律条余量归零并立后续新增须先压缩的呈报口径);art-prompts §5 九件工单判据由双档并列收成深档单档、§5.5 缺口 1/2/4 转已裁指路、余量口径三处一致;同批修一处术语闸红(主角形象描述用退役词「插旗」,改「配朱红矩形旗」)。遗留(下一批):#B07A14/#B8A582/#786E62 三枚合并目标色未在 §4.1 表逐字在册,须随色板检查器建成后对账 2026-10-28 05:50:05 +08:00
DKChan 50702ea506 美术(战斗一屏样张批·设计段闭合): 样张目标规格定值落 art-bible v3.21——§6.1 背景契约订正为 scene_stage_bg.png 并新增地台独立件/§4.1 第二道门按用户裁改宽松口径(不得三通道全<8)、清偿 6 对换色合并欠账、色板增列 #A61D37 转正与三处换色合并、余量 3→2/§4.2 血条填充显式归危险红 #A8321F、#689CE8 登记违例退役/§7.4 顺位徽标不增列豁免面、字重重开钩子注销/§8.2 护甲图标 24×24 与地台件 48×12、棋盘小人 32px 源档终值/§2 世界底层拆两子带(解决同层两底难辨,非 §1.4②项成立要件)/§8.4 方向集代拟口径追认/§6.3 三关色板终值取值程序登记/§7.6(三) 平滑件并存登记/§1.4 样张产出手段不进圣经;character-design §5.3 护甲三档转定稿;资产清单与路径图新增四件资产行与 scene/ 域键;待办清单样张条目记美术线闭合凭据(art-bible v3.21)并推进至待写卡,新登记三关色相×五组微光互辨抽测。定值推导链见 2026-10-28 设计轮过程稿(按吸收即死不入库,沿革以本条 commit 为准) 2026-10-28 04:32:31 +08:00
315 changed files with 16685 additions and 10040 deletions

No files matched your search

+20 -8
View File
@@ -16,7 +16,7 @@
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
- **交付前验收 = 跑 `tools/run_all_gates.sh`,按本批改动域选档(`--code` 只跑代码域/`--docs` 只跑文档域/不带参数=两域全量),所选域全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,**域划分也只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单。域按**文件性质**判:代码域=`game/` 下非 `.md` 的行为载体(代码/测试脚本/探针/场景/数据表/素材),`game/` 内 `.md` 与 `game/` 外一切路径=文档域;同批两类都有时跑全量档。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回时由 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-commit 钩子(`tools/hooks/post-commit`)自动追绿复验,绿态与无状态零触发——主区小批直提不重复跑全量,同一份字节禁止第二次人手跑。CI 红不回滚合并,红项在案、修复提交追绿;开工先读 CI 状态,红未修不开新批。跑批起手自动 `--editor --quit` 刷类名缓存(.godot 缓存不入 git,防合并后失配假红)。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起
- 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd`
- 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`)
@@ -40,10 +40,10 @@
| 时机 | 权威文档 |
|---|---|
| 批次开工 / 流程纪律 | `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 |
| 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) |
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 全量验证 / 验收清单落码 |
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 |
| 定文档方案 / 写文档 / 改文档前 | `design/engineering/doc-conventions.md`——白话写作 / 载体分工 / 用语 / 结构骨架 / 会话记录与生命周期 |
| 概念切分与互链 | `design/engineering/concept-conventions.md`——概念封装 / 跨文档引用 / 归档与事实源 |
| 系统设计 / 增删文档 | `design/gdd/02-系统清单.md`——根索引 + 中文规范名↔英文代号对照 |
@@ -77,16 +77,28 @@
### 格式优先级
枚举用列表,二维对照用表格,按内容形态选;散文是最后手段。
能画图的内容一律画图说明,图是第一优先载体;枚举用列表,二维对照用表格;散文是最后手段。
### 图优先
**判据**:描述含多个实体、实体间有明确关系(先后顺序、包含、依赖、状态转移、布局对比)的内容时,直接画图说明,不写长段文字;单点事实(一句装得下)不画,纯数据对照用表格。
**载体**:默认用 visualize 技能出 HTML/SVG(会话内联渲染,表现力优先),流程/状态/结构/实体关系与空间对比、像素级示意、界面草图同一通道;查看端不支持内联渲染时退回 Mermaid 代码块,仍不行用字符画。
**边界**:硬数值与前提条件留在文字里,图不承载唯一数值依据;图与文禁重复说同一件事,文字只留图说不清的部分。
## 主动检索提醒
- 训练记忆缺失或可能过时的问题,先搜最新主流的答案再回答。
## 硬性红线(勿违反;操作细则在 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-30 用户裁本地 CI 化)——一切面(含代码面 `game/` 下非 `.md` 的行为载体与 `.zcode/`、`.qoder/` 等不入 git 面)主工作区直改+直提;commit 点由 git pre-commit 钩子(`tools/hooks/pre-commit`,`core.hooksPath` 挂载)现场跑文档域闸,绿落地红拦,合并收尾提交跳过(归合回后 CI);代码面全量验证归合回后本地 CI(`tools/hooks/post-merge` 触发,见「运行与验证」首条);大批次(迭代实现批等)按约定仍走独立 worktree(冻结→件核→并 master→重塑→主区合回→CI 验证),约定非强制;`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
- **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节)
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
- 交付前验收 = 跑 `tools/run_all_gates.sh` **按改动域**全绿(唯一入口、唯一闸清单、唯一域划分,禁在文档另列);合回 master 由 worktree-guard 按 diff 文件性质推域校验回执强制(process-conventions §3);非代码面直改批的验收=commit 点 git 钩子自动跑 `--docs`(process-conventions §1)
- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录与两份根流程清单(`待办清单.md`/`体验验证清单.md`)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;批末只跑代码域闸。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,登记在案后由用户发令在主工作区直改+直提(commit 点 git 钩子自动跑文档域闸),完成即删条目;触及代码面的工程改动(如纯测试维护)仍走 worktree 批;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制)
- 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <5 个工作日=「冻结待凑批」、不可开工);流水线闸 `tools/pipeline_gate.py` 强制(已注册进 run_all_gates.sh 文档域);实现段零裁断——卡片没写清=上游缺陷,停该卡退回上游,禁内联求裁(process-conventions §2,格式与状态机=doc-conventions §12)。工程/文档设计/美术素材三类改动不走写卡与编排段:登记 →(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸 → 完成删条目(车道细则=process-conventions §1)
- 合回后验证 = 本地 CI 全量域闸全绿(唯一强制验证点;唯一入口、唯一闸清单、唯一域划分与选闸算法仍只在 `tools/run_all_gates.sh`,禁在文档另列;触发与追绿语义见「运行与验证」首条与 process-conventions §1)。开发期手动跑批按需选档(`--code` 关联档选测——02 跑「卡片申报 ∪ diff 推导」并集套件、三件保险恒跑、横切面回落全量——算法只在闸脚本内定义),手动跑不构成验收;全量回归由用户自发发起。文档面直改批验收=commit 点 git 钩子自动跑 `--docs`(process-conventions §1)
- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;合回后由本地 CI 全量验证(红项修复提交追绿,process-conventions §1)。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,由用户发令在主工作区直改+直提(commit 点 git 钩子自动跑文档域闸);大体量改动(含触及代码面的工程改动如纯测试维护)按约定走 worktree 批(非强制);文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制)
- 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <5 个工作日=「冻结待凑批」、不可开工);流水线闸 `tools/pipeline_gate.py` 强制(已注册进 run_all_gates.sh 文档域);实现段零裁断——卡片没写清=上游缺陷,停该卡退回上游,禁内联求裁(process-conventions §2,格式与状态机=doc-conventions §12)。工程/文档设计/美术素材三类改动不走写卡与编排段:(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸(车道细则=process-conventions §1)
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
- **验收闭合**——每个需求点与每个行为改动都要有从真实入口进入的验收断言,可 grep 定位、在当轮真实跑批中执行并通过,判据绑被测改动特有的可观测提交面;**禁变异类测试与撤销类取证**(2026-10-24/2026-10-28 用户裁——人为制造红属伪造证据),证明做不出=停卡呈报;禁删断言/放宽阈值换绿;合回前独立验收审计未过则禁止合并(细则=test-conventions §8+iter 技能第 5 步)
+18 -4
View File
@@ -81,7 +81,7 @@
| 钩爪恐魔 hook_horror | 普通怪(钩爪/多足轮廓注意凹凸计数控制) | light | 第三关群行捕食者,小体量可辨 |
| 灰矮人石君王 duergar_king | **Boss:锐角与不对称**,体量最大 | heavy | 第三关 Boss;紫赤色相独占+形状独占双通道 |
- **通用规格**:亮色描边+落影底盘轮廓通道硬规则;透明底 PNG;每敌种 2–3 版候选;护甲类型视觉区分方案已落 art-bible §5.3(候选方案,样张冻结终稿)。
- **通用规格**:亮色描边+落影底盘轮廓通道硬规则;透明底 PNG;每敌种 2–3 版候选;护甲类型视觉区分方案已落 art-bible §5.3(已定稿)。
- **来源**:✦ ComfyUI(prompt 卡 v2.0 §3,八敌种+L2 熊地精变体共 9 卡);理由同 §2.1——不复用。
- **联动**:显示名以 `data/tables/enemy.tres` 与敌人系统文档为准(L2 熊地精=bugbear 复用,显示名熊地精系)。
@@ -130,8 +130,8 @@
### 3.5 营地场景图
- **要什么**:营地场景图 9 张——`scene_camp_bg`(背景)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件)。钥石顶栏图标沿用现役 `icon_keystone`。
- **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#F6C667` + 木色 `#8A5A34`(art-bible §6.2);2× 源档出图、最近邻降采样到 1280×800;后处理=抠底/降采样/色板量化/描边统一。**每张出图须登记模型名+分辨率+seed+对话/日期**。
- **要什么**:营地场景图 10 张——`scene_camp_bg`(背景层,不透明全幅=天空+远山+整片地面+门洞内一条通向远处的小路)+ `scene_camp_wall`(围墙层,透明底=横贯全宽密排木桩墙+两根门柱,门洞留透明缺口)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件)。钥石顶栏图标沿用现役 `icon_keystone`。
- **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#F6C667` + 木色 `#8A5A34`(art-bible §6.2);2× 源档出图、最近邻降采样到 1280×800;后处理=抠底/降采样/色板量化/描边统一。**出图登记照 [remote-gen-prompts](remote-gen-prompts.md) 头注轻量口径执行:用途+生成时间必注,其余参数有则记。**
- **排期**:营地交互重做**美术自立批**(不占迭代位;UI 贴图重构批已搁置)。终稿落 `game/assets/art/scene/`,消费常量 `UiKit.TEX_CAMP_*`。
- **现状**:全占位。
@@ -141,6 +141,13 @@
- **来源/规格**:♻ 复用开源——缝合像素字体(Fusion Pixel,SIL OFL 1.1,授权总表在册);墨色走主墨系(art-bible §7.2)。换字体是全局决策;样张用字即此定稿件,过样张闸第 1 项(数字可读性 ≥4.5:1,禁以换字形逃避)。
- **现状**:✅ 已定稿在役(现状即终稿)。
### 3.7 护甲类型图标三枚 ♻优先/⚙兜底
- **要什么**:`ui_armor_light.png` / `ui_armor_ward.png` / `ui_armor_heavy.png` 三枚,站桩名字行与护甲图标同排挂载(原为 ◇/◆ 字形占位)。
- **风格/规格**:源 24×24、显示 24px = 1×(甲类整数倍缩放纪律,art-bible §8.2);形状 + 色彩双通道,灰度下按甲面覆盖率三档可辨(character-design §5.3 已定稿:轻甲覆盖率≈0/魔法护甲符纹饰带/重甲金属甲板);不触 §5.1 剪影 archetype。
- **来源**:♻ 素材站通用护甲/符纹图标改色优先,过 §0 复用闸四道;⚙ 兜底程序化。
- **现状**:未开始(占位 = 站桩名字行裸 12px 字形 ◇/◆,随本条到位后改挂图形件并把名字行提到 `FS_HINT` 14)。
---
## 4. P3 —— 演出类(战斗打磨项,样张后排期)
@@ -148,11 +155,18 @@
| 资产 | 需求 | 风格/规格要点 | 来源 |
|---|---|---|---|
| 受击反馈 / 死亡表现 / 顺位标记 | 敌人系统登记的资产需求**下限三类** | 死亡=血条归零后**灰化+碎裂退场,禁止瞬间消失**;顺位标记需在敌多时可读 | ⚙/✦ 挂自产精灵——死亡灰化碎裂是语义硬规则,不可整包复用;烟尘碎屑粒子可 ♻ 改色 |
| B 区舞台背景层 `scene_stage_bg_dungeon.png` | 战斗舞台背景·dungeon 变体(art-bible §6.1 资产契约) | **✦ 插画级**;源 = 基准屏宽 × `stage_h`(读布局表 `battle_layout.tres`)、B 区全高、显示 1×;顶部空区+纵深洞厅远景(远/中/近三层;暖石棕、宽岩台/层理、地精巢穴支洞/木栈道/木桩围栏),不画近景地面;按 `board_type` 选图,不走 `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) |
| 连锁坍塌显形演出 | 连锁展开触发时的连锁演出 | 与音频方向同批配对登记;「坍塌显形」=连锁展开的玩家可见名 | ♻ 粒子/碎块改色 + ⚙ 坍塌结构自产 |
| 封印特效 | 该动作的呈现名 = **封印**(art-bible §3.2):**施印瞬间特效 + 印记常显形态**两件 | 印记旗面走封印黑 `#000000`(现行封印色,色史归 art-bible §4.1);特效走运动+色相(§2:状态/情绪不走明度,空间光照可走);现行占位 = 封印黑「旗」字形;印记常显形态可与 §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. 明确不在本清单
@@ -161,7 +175,7 @@
- **秘宝图标**:内容归 Alpha 秘宝整体重设计,内容未定不能先做;渠道届时按 ♻/✦ 另评。
- **封面图/宣传图**:Alpha 后另批,不阻塞切片;需项目角色出镜,素材站合成观感差,宜 ✦。
- **地形变异专属纹理**:六类型已定稿,纹带需求 = 2 纹形(伤害轴刺缺形/信息轴遮蔽形)× 3 图类型色板,**样张后可排产**;形状通道规则见 art-bible §3.2。
- **精英/Boss 护甲类型视觉区分**:已落 art-bible §5.3(候选方案:甲面覆盖率×甲材质色三档,样张冻结终稿)。
- **精英/Boss 护甲类型视觉区分**:已落 art-bible §5.3(甲面覆盖率×甲材质色三档,已定稿)。
## 6. 执行顺序(依赖链)
+42 -22
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.21,历次修订明细见 git 提交史
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.30,历次修订明细见 git 提交史
>
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -57,7 +57,9 @@
**判定与回退**:第 1 项不过时按原则二设计测试回退——先调格底明度档,复杂格底垫色牌后备,禁止缩字号换字形逃避;第 2–4 项不过时修形不修数。样张通过 = 砖面形态与色板冻结;新增图类型/变异色板须重跑本测试(§4.3)。执行时机:正式砖面资产开工前;《HUD框架》以本节为引用依据。验收:机检〈样张机检断言集全绿后方可冻结砖面形态与色板〉
**整屏样张闸**:格子样张验"格",整屏样张验"屏"——战斗屏/关卡屏/菜单屏/营地屏各出一张整屏样张,验收四项:①全部文字(含菜单/说明/tooltip/按钮)对其直接背景 ≥4.5:1(WCAG AA),大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;②明度分层三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4:首焦唯一——数字区/立绘/主按钮,无第二焦点竞争;负空间服务焦点,不属空窗);④文字禁直接压纹理底(垫容器或罩层)。验收顺序 = **方向稿目检先行**(防"机检过闸但气质否决"式返工),目检过后机检冻结。执行时机 = 屏级视觉落码开工前;过闸前一切屏级视觉为占位临时态(§7.1 完成条款)。验收:机检〈整屏样张四项断言全绿,且目检先行过审〉
**整屏样张闸**:格子样张验"格",整屏样张验"屏"——战斗屏/关卡屏/菜单屏/营地屏各出一张整屏样张,验收四项:①全部文字(含菜单/说明/tooltip/按钮)对其直接背景 ≥4.5:1(WCAG AA),大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;②明度分层三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4:首焦唯一——数字区/立绘/主按钮,无第二焦点竞争;负空间服务焦点,不属空窗);④文字禁直接压纹理底(垫容器或罩层)。验收顺序 = **方向稿目检先行**(防"机检过闸但气质否决"式返工),目检过后机检冻结。执行时机 = 屏级视觉落码开工前;过闸前一切屏级视觉为占位临时态(§7.1 完成条款)。**样张判定物的产出手段与素材输入级别不进圣经定值**——圣经只定样张要验什么,不定样张以何种手段产出、也不定它取哪一级别素材输入。验收:机检〈整屏样张四项断言全绿,且目检先行过审〉
**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸待方向稿目检先行(真人检测,登记于体验验证清单 ㊶)
**样张现状**:格子样张已冻结,冻结凭据 = git 提交 7a847c7(机检报告)与 §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 亮墨三档直画 + 色牌后备(值见 §4.1);新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉
@@ -68,6 +70,8 @@
> 本节服从第 1 节宣言「明快的地牢,轻快的扫荡,仔细的推理」。能量值为全作统一 0–10 刻度:0 静谧,10 沸腾。
>
> **明度分层(硬规则)**:明度按视觉层分带——**信息面层**(数字/UI 数据/预告/语义标识)最高、**世界中层**(格底顶面/角色/物件)居中、**世界底层**(背景/纵深/远景/已开空间)可更低;层间明度差服务空间纵深与光照(Moonlighter 式洞穴层次)。**情绪明暗禁令**:明度变化禁止用于**情绪表达**——画面禁止靠压暗/渐晕阴沉下来;roguelike 失败重开,世界无光暗记忆;Darkest Dungeon 式压暗仍在 §9.2 反面清单。**状态表达不受本禁令约束**:状态可用明度、色相、饱和、运动、元素任一手段承载。**允许域**:空间纵深、光照氛围、形体塑造。类型微光的明度梯度(§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〉
### 2.1 挖掘阶段(核心状态,占时最长)
@@ -203,18 +207,27 @@
- **收益金亮档 `#E0B860`(GOLD_BRIGHT)**|UI 徽标可读性亮档 token:敌顺位徽标①②③与重甲徽标对舞台底 6.47:1(GOLD_DEEP 3.26:1 不达 §7.2 带,故另立亮档);金族亮档,与奖励金 #F0A828 同族不同岗——奖励金 = 已兑现收益语义(§4.2 金条),本 token 只承载徽标可读性、不占收益语义。GOLD_DEEP/HERO_BLUE 常量本尊不动(standee 意图判据等语义岗现行使用)。验收:机检〈GOLD_BRIGHT 对舞台底 ≥4.5:1〉
- **小人蓝亮档 `#7EBAF0`(HERO_BLUE_BRIGHT)**|魔法护甲徽标对舞台底 5.88:1(HERO_BLUE 3.23:1 不达带,故另立亮档);蓝族亮档,与小人蓝 #2E86DE 同族不同岗——本 token 只承载徽标可读性,不占用「蓝只说己方」的语义。验收:机检〈HERO_BLUE_BRIGHT 对舞台底 ≥4.5: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 档〉
- **站桩石板地台板身 `#968264`(STAND_STONE)**|战斗屏场景环境子板(分层②,不占 48 token 额度):B 区站桩地台板身与顶缘的材质色,跨三关恒定、不吃 §4.3 `wall_ambient` tint(地台是 §6.1 定死的世界中层承载面,随关卡变会破 §2 明度分层的层间关系)。转正前即在役。验收:机检〈战斗屏场景环境子板色数 ≤32 且本值在册〉
- **重甲冷灰金属材质色 `#868C9A`(ARMOR_PLATE_COLD,token 名暂拟、随实装批登记)**|语义/UI token 板(**分层①**,占 1 枚额度,**余量 2→1**;用户裁):重甲档甲材质色;character-design §5.3 通道 = 甲面覆盖率 × 甲材质色,甲材质色按甲档统一,故石君王岩石板甲**同引本枚、不另计额度**;材质色跨屏恒定,落分层①不落分层②(环境色才随场景走)。过门情况:第二道门按宽松口径(禁止三通道全 <8)与在册色逐一比对,**无三通道全 <8 的对 → 过门**;§4.2 语义检查过门(色相 222°,不占红/金/蓝/紫赤/绿语义段、不在 300°–345° 禁入带;材质色不进信息层与语义物件岗)。对比实测:对深素底 `#282220` 4.65:1、对舞台底 `#3E362C` 3.52:1(图形 ≥3:1 门槛);相对亮度 L≈0.261 落 §2 世界中层 0.22–0.45 带。验收:机检〈本值入板后全板色对中三通道差全部 <8/255 的对数 = 0〉
- **卓尔浅紫灰皮 `#A79CB8`(DRIDER_SKIN,token 名暂拟、随实装批登记)**|语义/UI token 板(**分层①**,占 1 枚额度,**余量 1→0,额度用尽**;用户裁):蛛化卓尔精灵上体皮色(体块主色,L≈0.355 落 §2 世界中层带)。过门情况:第二道门同上行口径,**无三通道全 <8 的对 → 过门**;§4.2 语义检查过门(色相 264°,避开 300°–345° 禁入带,紫赤独占仍归 `#8F2F6A`;不占本节五语义段)。对比实测:对深素底 `#282220` **6.04:1**、对舞台底 `#3E362C` **4.57:1**——**两处均过图形 ≥3:1 门槛**(本轮新算)。验收:机检〈本值入板后全板色对中三通道差全部 <8/255 的对数 = 0〉
- **换色合并登记五处(用户裁,部件色落值)**|按本表「不过门即触发换色合并、禁加色绕行」执行,五处**零新增额度**(各件原拟新增候选一律不取):① 主角铲刃浅铁灰(原拟 `#A8AEB8`)→ 并入 `FOG_MEMORY_INK #C4C4C4`,**部件取值 = `#C4C4C4`**;② 主角提灯铜色(原拟 `#C08A3A`)→ 并入 `GOLD_DEEP #B07A14`,**部件取值 = `#B07A14`**;③ 食人魔酋长暖褐皮(原拟 `#B08050` / `#A97C4E`)→ 并入 `暖石 #B8A582`,**部件取值 = `#B8A582`**;④ 石君王灰皮(原拟 `#9A968C` / `#949089`,按**次级皮**口径——板甲视同体块主色、灰皮属次级皮故不受 L 0.22–0.45 约束)→ 并入 `STONE #786E62`,**部件取值 = `#786E62`**;⑤ 轻甲护甲图标木色 `#8A5A34`(深素底下 2.72:1 不过门;原拟新增木色亮档 `#C49A5E`)→ 并入 `WARM_FAINT #CDB88C`,**部件取值 = `#CDB88C`**(8.08:1)。**一色两岗代价已随裁定接受**:① 的 `#C4C4C4` 语义岗 = 迷雾记忆墨(§6.5)、② 的 `#B07A14` 属金族(§4.2 金只说收益与兑现)——两者均作角色随身件材质色用,不进信息层与语义物件岗;③④⑤ 无语义岗冲突。验收:机检〈五处部件取值点全部改指合并目标色,四枚原拟新增候选值零入板〉
- **换色合并登记三处**|按本表「不过门即触发换色合并、禁加色绕行」执行,三处零新增额度:①`#C4AC84`(地台顶缘受光线,代码 `STAND_STONE_RIM`)→ 并入 `#C9AF83`(掩尘,并与 §8.2 高墙口沿高光 `Color8(196,172,132)` 统一为同一枚);②`#5A4C3A`(地台描边,代码 `STAND_STONE_EDGE`)→ 并入 `#4C4038`;③`#3E342A`(舞台底,代码 `STAGE_BG`)→ 并入 `#3E362C`(状态槽底)。①②的绘制点全部改指合并目标色、源常量删除——程序化回退绘制与已落合并值的贴图保持一致;③随 §6.1 舞台背景插画批一并处置:背景资产接管该绘制面时本常量随绘制点退役或改指,禁单独为改值出批。验收:机检〈①②两枚源常量全仓零引用,绘制点全部改指合并目标色〉
**对比硬指标(可测)**:数字可读性由**格底明度分档**承载——棋盘数字亮墨 **L≥0.75** 对**已开格底 L 0.08–0.14** ⇒ 直画对比 **≥4.5:1 恒成立**(WCAG AA,样张逐组合抽测冻结);**色牌后备**:色牌素色恒定 **L≥0.70**、牌上数字深墨 **L≤0.027** ⇒ 任意牌墨组合 ≥4.5:1(实测样例:#33261B/#F5EAD3 = 12.3:1)。未开格底槽(含全部图类型)相对亮度 **L≥0.30**(墙顶面受光档)、HSV 饱和 **≤0.55**、避 Boss 禁入色相带;已开格底走世界底层档(见上)。物件识别不依赖格底(§5.1 轮廓通道),旗面、金色不作识别依赖。验收:机检〈亮墨对已开格底 ≥4.5:1;未开格底 L≥0.30、饱和 ≤0.55;任意牌墨组合 ≥4.5:1〉 ←原二;地形变异 AC-9
**色板增量纪律(数值状态 = 待定现用)**:全作色板硬上限 **48 色**(防色板无限膨胀的封口条款;额度余量以色板检查器实报为准);新色入板须同时过两道门:①§4.2 语义检查(不占语义色段/禁入带);②**与既有任一色的 RGB 三通道差 ≥8/255**——反例实证:#3A2E22 ↔ #362B20 三通道差均 <5,肉眼不可辨,同板共存只增噪。不过门即触发「换色合并」(把意图色并进最近既有色),**禁加色绕行**;加色义务同时登记于本表修订。**「待定现用」态释义**:数值**现行即执法**(色板检查器按此拦,§8.3)——非候选(不存在"等冻结"空窗)、非死规(后续需求引入时可变);调整走本节修订并登记,改值 = 修订不是违约。验收:机检〈色板总数 ≤48;新色入板过两道门〉
**色板增量纪律(数值状态 = 待定现用)**:全作色板硬上限 **48 色**(防色板无限膨胀的封口条款;**额度已用尽、余量 0**,后续余量以色板检查器实报为准);新色入板须同时过两道门:①§4.2 语义检查(不占语义色段/禁入带);②**与既有任一色的 RGB 三通道差禁止全部 <8/255**——即三通道里至少一通道 ≥8/255 即过门,「三通道差全部 <8」才算不过门(条文原写的「三通道差 ≥8/255」有「逐通道全过」与「禁止全灭」两种互斥读法,前者下现行色板 247 对不达标、与本节自认的「现行即执法」合规状态不能同真,故取后者;用户裁)。**额度已用尽:后续任何新增须先压缩或合并既有色**——按本节「额度不足时呈报作者」口径呈报,**压缩或合并哪几枚由作者裁,本节不代裁、不预指对象**。反例实证:#3A2E22 ↔ #362B20 三通道差均 <5,肉眼不可辨,同板共存只增噪。不过门即触发「换色合并」(把意图色并进最近既有色),**禁加色绕行**;加色义务同时登记于本表修订。**「待定现用」态释义**:数值**现行即执法**(色板检查器按此拦,§8.3)——非候选(不存在"等冻结"空窗)、非死规(后续需求引入时可变);调整走本节修订并登记,改值 = 修订不是违约。验收:机检〈色板总数 ≤48;新色入板过两道门〉
- **色板分层**:色板分两层管理——① **语义/UI token 板**:全作共享,硬上限 **48 色**,管 UI 语义色、墨色、材质色,跨屏恒定;② **场景环境子板**:**每屏一套,≤32 色,允许跨屏不共享**(环境色 = 天空、地面、植被、石材、火焰等随场景走的颜色)。新增环境色仍须过两道门(§4.2 语义检查 + 与既有任一色 RGB 三通道差 ≥8/255),但**不占用 48 token 额度**;不过门照旧走「换色合并」,**禁加色绕行**。单张 PNG 的像素域色数上限 **≤32 色**另由 `standee_asset_check` 执法(按单图像素域取值方式计数,与本表 token 计数互不干扰)。额度不足时向作者报告,由作者决定压缩或合并既有色。验收:机检〈token 板 ≤48;每屏环境子板 ≤32〉
- **换色合并欠账清单(全板重扫 6 对不达标,逐对清偿)**:`#18120C` ↔ `#181410`、`#33261B` ↔ `#362B20`、`#362B20` ↔ `#3A2E22`、`#3E362C` ↔ `#3E3731`、`#3E4048` ↔ `#3F4147`、`#453723` ↔ `#4A3B2A`——六对按「不过门即换色合并」纪律逐对清偿(并进最近既有色);清偿前该对不视作合规,清偿动作落本节修订。**本节所举反例 `#3A2E22` ↔ `#362B20` 正在这 6 对之内**,与本口径自洽。验收:机检〈全板色对中三通道差全部 <8/255 的对数 = 0〉
- **色板分层**:色板分两层管理——① **语义/UI token 板**:全作共享,硬上限 **48 色**,管 UI 语义色、墨色、材质色,跨屏恒定;② **场景环境子板**:**每屏一套,≤32 色,允许跨屏不共享**(环境色 = 天空、地面、植被、石材、火焰等随场景走的颜色)。新增环境色仍须过两道门(§4.2 语义检查 + 与既有任一色 RGB 三通道差禁止全部 <8/255),但**不占用 48 token 额度**;不过门照旧走「换色合并」,**禁加色绕行**。单张 PNG 的像素域色数上限 **≤32 色**另由 `standee_asset_check` 执法(按单图像素域取值方式计数,与本表 token 计数互不干扰)。额度不足时向作者报告,由作者决定压缩或合并既有色。验收:机检〈token 板 ≤48;每屏环境子板 ≤32〉
### 4.2 语义色彩规则
> **适用域**:本节「X 只说 Y」约束**信息层**(格内信息/UI 数据/预告/语义标识)与**语义物件**(GP=收益金、血条=危险红等);**纯环境氛围**(背景/装饰变体/远景/非语义物件)不受色相独占约束,改受 §3.4 焦点预算与低饱和后退约束(不抢读即可用色)。禁入带 300°–345°:信息层与语义物件禁入不变;环境层可入但限低饱和(高饱和紫赤仍 Boss 独占,§2.6)。验收:目检〈信息层与语义物件不落禁入带、不占语义独占色相;环境层用色低饱和〉
- **红只说危险**:危险红 = 兑现档,红族单档。禁(信息层域):红禁止用于奖励、装饰性标识与常规 UI(微光群伤组除外——见「类型微光色带」条,其色相刻意错开语义红)。验收:目检〈信息层域红色仅出现在危险语义与微光群伤组〉 ←原三;P2;§2.1
- **血条填充 = 危险红 `#A8321F`**(语义物件显式落法):血条是本节适用域点名的语义物件,填充色族一律归危险红,不因敌我分色、不因关卡换族。读数不依赖填充色对比——血量数字 11px 压 `HP_BAR_BACK` 素牌(§7.4 豁免①,14.3:1),填充只承载「剩余比例」的形状语义。**蓝族引擎默认近似值 `#689CE8` 登记为违例值、已退役**:它同时撞本条「血条=危险红」与蓝条「蓝禁止表示敌方危险」,禁再入任何血条绘制点。验收:机检〈血条填充绘制点取值为 #A8321F 且全仓零 #689CE8 字面量〉
- **金只说收益与兑现**(奖励、结算、胜利)。禁(信息层与语义物件域):金禁止作危险障碍色、不上格底、不作日常 UI 强调(防通胀;微光通用增益组的芥末黄属「类型微光色带」条色带,非收益语义金)。验收:目检〈信息层与语义物件域金色仅承载收益与兑现〉 ←P2;P3;§2.2–2.4
- **蓝只说己方与冷静信息**(小人、光标、工具信息——含可挖域接触带呼吸微光,蓝族浅一档 `#D6ECFF`)。禁(信息层域):蓝禁止表示敌方危险;暮蓝 `#5A6C8C` 为失败独占(§2.5),只作全屏氛围,不作物件色。验收:目检〈信息层域蓝色仅承载己方与冷静信息;敌方危险无蓝色〉 ←P1;§2.5
- **紫赤只说 Boss 与精英** `#8F2F6A`(色相 323°):郑重来自色相独占,不来自压暗。禁:**HSV 色相 300°–345° 为信息层与语义物件禁入带**——UI、粒子、类型微光、语义物件及图类型色板信息面禁止落入;环境层(背景/装饰)可入但限低饱和,高饱和紫赤仍 Boss 独占。验收:机检〈信息层与语义物件色值色相不落 300°–345°;高饱和紫赤仅 Boss 战〉 ←§2.6;原三
@@ -337,7 +350,7 @@
| 线索通道 | 冻结内容 |
|---|---|
| 意图姿态 | **动画三档**:① 普通攻击 = 常态待机(零独立资产);② 攻击技能 = 特殊准备动画(放光/高举武器);③ 重击/硬化类 = 蓄力动作或防御姿态。不显示文字与技能名(信息定义见《敌人系统》意图线索节);三档在灰度/剪影层可辨(双通道纪律) |
| 强度档位 | 体量通道:§5.1 archetype 体量/垂直延伸("大个子多半有重击");护甲类型(light/ward/heavy)视觉区分方案已落 §5.3(候选,样张验收时冻结) |
| 强度档位 | 体量通道:§5.1 archetype 体量/垂直延伸("大个子多半有重击");护甲类型(light/ward/heavy)视觉区分方案已落 §5.3(已定稿) |
| 预告时序 | 预告语义见《敌人系统》意图线索节,呈现义务(挖掘期全程可见)见《HUD框架》;本表只锁「结算不另设独立意图播片」的动画形态 |
| 恒定义务 | enemy_id 键映射跨 run 恒定、禁重掷;图像不报数——精确数值的明细承载见《战斗结算》。验收:机检〈enemy_id 键映射跨 run 恒定、禁重掷〉 |
@@ -352,7 +365,7 @@
> **本节内容已整体迁至独立文档 [`character-design.md`](character-design.md)**(小节号 §5.1–§5.6 在该文档中沿用不变)。迁移动机:人设高频改动,独立成档免动圣经;本存根保证既有 "art-bible §5.x" 式引用(§4.1/§4.5/§4.6.4、art-assets-checklist、战斗表现)经此仍可达,新引用直指该文档。
内容速览:§5.1 角色剪影与轮廓通道 · §5.2 头身比与体量档位 · §5.3 护甲类型视觉区分(候选,样张冻结终稿) · §5.4 主角三态规格(形象锚 = 蓝兜帽披肩的暖装小冒险者:圆头挖掘铲 + 冒险背包插朱红矩形旗(与棋盘封印色脱钩) + 护目镜腰灯) · §5.5 八敌种逐卡 · §5.6 意图准备动画三档。
内容速览:§5.1 角色剪影与轮廓通道 · §5.2 头身比与体量档位 · §5.3 护甲类型视觉区分(已定稿) · §5.4 主角三态规格(形象锚 = 蓝兜帽披肩的暖装小冒险者:圆头挖掘铲 + 冒险背包插朱红矩形旗(与棋盘封印色脱钩) + 护目镜腰灯) · §5.5 八敌种逐卡 · §5.6 意图准备动画三档。
---
@@ -363,16 +376,21 @@
### 6.1 战斗舞台背景(B 区环境)
- **双舞台常驻下的 B 区环境**:战斗画面区(站桩立绘所在)的背景 = 明亮地窟,与 §1 风格基调同源——暖倾向、无压抑;背景整体**低对比自然后退**(§3.4 焦点预算延伸):背景禁止构成完整凸形、禁止抢 B 区立绘与 D 区数字的焦点,识别层级恒为 立绘 > 背景。验收:目检〈背景无完整凸形、立绘最抢眼〉
- **舞台正面目标**:B 区 = **地窟看台**——立绘脚下地台/落影承载(世界中层),背景环境层低饱和后退、明度走世界底层档,允许光源氛围(洞顶光柱/暖光晕候选);角色禁止直接贴平色底。规格与终值归整屏样张闸(§1.4)冻结。验收:目检〈立绘脚下有地台/落影,非贴平色底〉
- **舞台正面目标**: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 同款逻辑)。验收:方向性
- **图类型联动**:背景 tint 走 §4.3 `wall_ambient` 槽,随三关换挡(方向候选见 §6.3);紫赤仅 Boss 战允许短暂进入背景氛围纹样(§2.6 色相独占的舞台侧延伸,高饱和独占不变),非 Boss 战高饱和紫赤禁入。验收:机检〈背景吸色无非 Boss 高饱和紫赤〉
- **资产渠道**:✦ 插画级(演出类场景贴图,清单 §4 联动登记);战斗一屏样张(§1.4 清单 1.2)含背景出样。**资产契约(尺寸候选,样张冻结终稿)**:基准一张 = `stage/stage_bg.png`(源 1280×270,B 区全高含明细带底;三关换挡走 §4.3 `wall_ambient` tint 槽,不按板出三张——换皮成本复用棋盘同款逻辑),显示 1×;呈现约束按本节各条(低对比后退/明度世界底层档/光柱氛围保留)。程序化平色+光柱现行使用 = 占位;插画工单归 PRD,交付后 art-status-checklist 同步 ✅。
- **图类型联动**:背景层按 `board_type` 选三张变体(`scene_stage_bg_dungeon.png` / `scene_stage_bg_underdark.png` / `scene_stage_bg_deep_underdark.png`,方向取 §6.3 三关色板候选);**背景不再走 §4.3 `wall_ambient` tint**(该槽保留给棋盘/世界的墙雾氛围,避免对三张背景二次染色)。紫赤仅 Boss 战允许短暂进入背景氛围纹样(§2.6 色相独占的舞台侧延伸,高饱和独占不变),非 Boss 战高饱和紫赤禁入。验收:机检〈背景吸色无非 Boss 高饱和紫赤;背景素材不引用 `wall_ambient` 槽〉
- **场景构成(2026-10-31 用户裁)**:B 区舞台从远到近分三段——**顶部空区**:洞顶以上的昏暗空间,只留深色空区,**不画钟乳石、吊锥、悬挂物或任何可辨形状**;**远景(纵深洞厅)**:不是贴墙的后壁,按远/中/近三层组织——远层暗剪影(远处洞口/通道、层叠岩体、洞底轮廓)、中层主要细节层(岩台、岩柱、凹壁、裂缝)、近层左右暗色岩框;剪影被中层部分遮挡、不贴顶、不孤立,**禁贴墙感、禁单件圆柱贴顶、禁砖石墙/规则砌缝/方整石块**;三关母题(地精=支洞/木栈道/木桩围栏;卓尔=巨柱/石笋/蛛网/螺旋塔剪影/巨菇/石桥;灰矮人=石笋城郭/棱角石厅/石桥/矿道口/烟囱塔)见 `battle-stage-prompts.md` §3.2;**分隔带**:远壁根部与近景地面之间一条不规则碎石带/阴影缝,边界清楚但不笔直,**禁瓷砖缝、踢脚线、规则台阶**;**近景地面**:干净压实泥土为主,全幅只零星几颗小石、散点分布,中央平缓留空给角色站位,**禁成排/成堆碎石、满铺砂砾、泥点噪声、瓷砖、规则铺装、草、木地板**。纵深与三段构图归 §1.4 样张;逐条要素、分层提示词与否定后缀 = `battle-stage-prompts.md`(本文件不重复维护)。验收:目检〈顶部无钟乳石/吊锥;远景有纵深、非贴墙;无孤立圆柱贴顶;近景为泥土碎石;分隔带不规则;中央无大障碍〉
- **图层归属(2026-10-31 用户裁)**:背景层 = 三关变体 `scene_stage_bg_dungeon/_underdark/_deep_underdark.png`(顶部空区+纵深洞厅远景,不画近景地面;按 `board_type` 选一张);近景地面 = **独立连续地面层** `scene_stage_ground.png`(泥土/砂砾/碎石地面,全宽、底边贴 B 区底、顶缘即分隔带;高度读布局表 `ground_h`,字段由地面层实装批新增并回填,本批终稿高 100、代码批落表);现有每角色小石台 `scene_stage_plaza.png` 保留,叠在连续地面之上。连续地面层与小石台**三关共用**、跨关恒定、**不吃 `wall_ambient` tint**(世界中层承载面随关卡换明度会破 §2 层间关系);背景三张变体各自带关卡色族,不再叠 tint。验收:机检〈三张背景文件名带 board_type 后缀;地面层/小石台文件名无 board_type 后缀;背景与地面层均不引用 `wall_ambient` 槽〉
- **资产渠道与契约**:✦ 插画级(演出类场景贴图,清单 §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 营地场景
- **情绪与色域**:承载 §2.8——全作最暖:烛光黄 `#F6C667` × 木色 `#8A5A34`(§7.2),中等饱和,无棋盘式高对比。
- **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=木围墙(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**(门洞内一条通向远处的小路);近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙(围墙是纯背景板),唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。
- **交付形态**:**背景 1 层 + 七个物件各 1 层**(**帐篷**为承载件、不可点),共 8 张透明 PNG + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。
- **场景构成**:一张可点击的营地场景图(1280×800,与工程档同幅面)。远景=天空与远山;中景=**独立成层的木围墙**(密排竖木桩、桩顶削尖、不透缝无横木)与**半开的营地大门**;近景=中央篝火、左侧锻炉铁砧、右侧帐篷与宝箱、帐篷旁的笔记本,另有木桩与挂在围墙上的布告板。**中景带自底部往上约 2/3 处起**;**近景物件可覆盖围墙,唯一例外=中间门洞必须留空、不被任何物件遮挡**。七个可点物件的功能绑定归 `camp.md`,本节只定画法。
- **图层拆分(10 张)**:场景按「远景/中景/近景要素各自成层」拆——`scene_camp_bg` 背景层(z0,不透明全幅)=天空+远山+整片地面+门洞内一条通向远处的小路;`scene_camp_wall` 围墙层(z1,透明底)=横贯全宽密排木桩墙(左右两段)+两根略高的门柱,**门洞处留透明缺口**透出背景层的小路;`scene_camp_board` 布告板(z2)、`scene_camp_gate` 门扇(z3)、`scene_camp_dummy` 木桩/`scene_camp_forge` 锻炉/`scene_camp_tent` 帐篷(z4)、`scene_camp_chest` 宝箱/`scene_camp_notebook` 笔记本(z5)、`scene_camp_campfire` 篝火(z6,最前)=八个物件层,各透明底单件一张。**帐篷**为承载件、不可点。围墙独立成层的好处:桩高可单独锁死、门洞做透明缺口、背景层不混入暖木色。提示词与坐标 = `camp-scene-prompts.md`,消费常量 = `asset-path-map`。
- **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条 `(0, 240, 1280, 70)` 裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB)。
- **色彩与氛围依据**:营地场景的冷暖分工(冷的大面积后退、暖的小面积前进)、空气透视三档与大面积用色规则 = `reports/提案-营地色彩与氛围脚本-2026-10-23.md`。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。
- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。
@@ -381,7 +399,8 @@
### 6.3 三关图类型色板候选(board_type 换挡方向)
- **模板与不变槽**:换皮只换 §4.3 四槽(`tile_closed`/`tile_open`/`wall_ambient`/`variant_tint`),信息与语义层全图类型恒定——本节只给三关**方向性候选**,终值按 §4.3 现行规则重跑 §1.4 格子样张冻结。
- **模板与不变槽**:换皮只换 §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 下探过渡):色相层进预告「更深更险」,明度档不动——
- **dungeon 地精洞窟**(第一关):现行掩尘系暖石棕(`#C9AF83` 基)即基准档,`wall_ambient` 暖棕;
- **underdark 幽暗地域**(第二关;方向:**冷调幽暗**):主调**灰白蓝**候选(低饱和蓝灰域,`tile_closed`),少量紫黑倾向点缀——紫系取紫罗兰域(≈256°)候选,禁入带 300°–345° 不变;`wall_ambient` 冷蓝灰;
@@ -424,7 +443,7 @@
### 7.2 UI 色板细则(自 §4.4 迁入)
**全局文字可读性硬指标**:**一切文字**(含菜单/说明/tooltip/按钮/底栏,无区域例外)对其直接背景 **≥4.5:1**(WCAG AA);大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;**文字禁直接压纹理底**——垫容器或罩层。核验闸 = §1.4 整屏样张。验收:机检〈ui_contrast_probe 全文字位 ≥4.5:1〉
**全局文字可读性硬指标**:**一切文字**(含菜单/说明/tooltip/按钮/底栏,无区域例外)对其直接背景 **≥4.5:1**(WCAG AA);大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;**文字禁直接压纹理底**——垫容器或罩层。**站桩石板地台面同理**(显式落法):地台板身 `#968264` 是 §6.1 定死的世界中层承载面,暖白 `#F0E2C8` 对其实测仅 2.89:1,文字禁直接压地台面——须垫容器、素牌或罩层,或落进结算明细带的素底。核验闸 = §1.4 整屏样张。验收:机检〈ui_contrast_probe 全文字位 ≥4.5:1;B 区站桩文字位正下方像素非地台板身色 #968264〉
**数据素面的墨来自世界的墨**:UI 数字、进度、准线一律主墨 `#33261B` 系——棋盘与面板读同一种墨;容器穿木石材质色,数据只穿墨。**营地与棋盘**:菜单容器背景用全作最暖的烛光黄 `#F6C667` 与木色 `#8A5A34`(§2.8;场景构成 §6.2);战斗容器转中性石色,防暖色渗入棋盘分走数字对比预算——氛围色向可读色让路。UI 不新增语义色,一切强调沿用 §4.2。**暗底呈现态双色墨制**(样张冻结):暗底系(已过/未来/候选/Boss 方块、暗数据框)图标与读数一律暖白 `#F0E2C8`(WARM)、亮底(当前金 `#F0A828`)转深墨 `#181410`(INK_ROUTE)——各态对比 ≥4.5:1;已过节点暗金冻结 `GOLD_DONE #68562C`(专用呈现态值;旧 GOLD_DIM 对双色墨不达标、已退出该呈现位);Boss 描边 = 紫赤 2px + 外缘 1px 深墨环(灰度冗余通道——§4.2 色相独占本色不变,§4.5 灰度可分类承载)。验收:机检〈暗底/亮底各态文字对比 ≥4.5:1〉
@@ -433,7 +452,7 @@
### 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 恒定;数据区直角细线不占圆角预算。终值归样张。验收:目检〈间距落在步进档〉
### 7.4 字体与字号
@@ -441,8 +460,8 @@
- **字体**:全局一套像素字,单套即终稿——缝合像素字体 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 基准,同字号实际更小)。验收:机检〈静态文字位点字号 ≥ 档位表值〉
- **字号档位表(六档)**:全站 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 位须同批增补本清单。验收:机检〈豁免文字位均可对上本清单,清单外无 <14px〉
- **字重纪律(字重两档不启用)**:层级手段为两件——**字号档差(相邻层级 ≥1.25×)+ 墨色深浅**;全局单字重,不上第二个字重(重开钩子:战斗一屏样张设计轮若实证标题层级不足,字重议题随该轮重开,不另立条目);数字禁描边/阴影伪加粗不变(字形为唯一数值载体,§4.1 同源纪律)。验收:机检〈主题仅注册单字重〉
- **小字号豁免条款(合并单条不开两口子)**:下款两类**豁免于本节下限**——①**条带内读数**:压在窄条带(血条/进度条等 ≤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 同源纪律)。验收:机检〈主题仅注册单字重〉/目检〈屏内标题与正文层级仅靠字号档差与墨色深浅两件即可分辨,无一处需要第二字重才读得清〉
### 7.5 营地界面
@@ -473,7 +492,7 @@
**(三)氛围层平滑件与抖动派并存**
- vignette / gradient_base / beams 三件程序化平滑渐变件**保平滑连续 alpha**(已裁),与卡顶类型色带 DitherBand 的 Bayer 有序抖动派**并存不一致**——两派统一裁决留**整屏样张**(§1.4;战斗一屏样张未开工,待办清单在册),本节只登记并存事实与裁决去向,不提前统一。验收:方向性
- vignette / gradient_base / beams 三件程序化平滑渐变件**保平滑连续 alpha**(已裁),与卡顶类型色带 DitherBand 的 Bayer 有序抖动派**并存不一致**——**整屏样张已裁:维持三件保平滑,并存事实按本节登记,不再统一**。裁定的依据:三件保平滑有用户裁背书、且已在 §8.2 显式 LINEAR 豁免清单在册(该清单写的是「怎么继续维护」不是「要不要取消」),统一派等于推翻用户裁并清空该清单;「把两派调参拉到同一颗粒感知档」在圣经里无任何条款依据,属自造动作。本节只登记并存事实,不统一。验收:方向性(目检复核:三件氛围件与卡顶抖动带同屏时,两派颗粒语言差异不构成观感冲突)
> **主题色值双源注记**:主题色值现存两处——`main_theme.tres` 字面量与 ui_kit 常量(README 溯源表缓解)。长期收敛候选 = 程序化 Theme 生成回代码单点,属重构面、未排期;新增色值仍按现状双落并同批登 README 溯源表。
@@ -495,15 +514,16 @@
- **缩放纪律(硬规则,按资产两类分级;不做个案豁免)**:
- **甲类·文本与语义件**(数字、图标、旗、格面纹样):维持严格整数倍缩放硬规则——可读性与语义精确性优先,**禁非整数缩放不变**。砖面 tile 不属本类,随格边长整格铺满(见 §8.1)。验收:机检〈甲类件显示尺寸为源整数倍〉
- **全局纹理过滤与平滑渐变件例外**:`project.godot` `default_texture_filter=0`(Nearest)全局——像素风 UI 皮(九宫格/主题石纹样式盒/滑轨抓子/勾选/箭头图标)翻 Nearest=意图面;**显式 LINEAR 豁免清单(有意例外)= vignette / gradient_base / beams 三件程序化平滑渐变件**——保平滑连续 alpha 的程序化氛围件(§7.6 三已裁);新增平滑件须同批入册本清单,禁静默挂 LINEAR。翻转面枚举与冒烟断言(6 条)在闸。验收:机检〈过滤面枚举与 LINEAR 清单逐项一致〉
- **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×)。验收:机检〈显示档与登记表一致〉
- **显示档登记例外(64 源 0.5×,待素材批消除)**:柜台四枚 `icon_forge/workshop/atlas/keystone`(64×64 源)显示档统一 **32px = 0.5×**;状态九枚(32×32 源)mini(站桩)档 **16px = 0.5×**。两处均系「显示尺寸侧唯一可调、素材一字节禁改」红线下的人工检整数半倍档,登记为**待消除例外**——素材设计出 32px 源后回归 1×。常规档显示一律 1×(状态九枚 32px、手绘十枚 20px/40px 双档、BCard 图标槽 40px=2×、**护甲类型图标三枚 24px=1×**——源 24×24,归甲类整数倍纪律)。验收:机检〈显示档与登记表一致〉
- **乙类·角色与单位精灵**(有机形体、非文字):允许运行时非整数倍缩放(NEAREST 过滤不变),容忍轻度像素宽窄不均;判据 = 实机目检,可感不适即回整数档。乙类免逐案豁免。验收:目检〈立绘无可感拉花〉
- **站桩立绘槽位=高定宽随**:立绘/精灵槽位只定「高度的整数倍档」,宽度随源图宽高比自适应——**禁止把全部立绘塞进统一方框强拉**(纵横倍率必须恒等)。反面实例:统一方框把窄条生物源(细长本体裁掉透明边)按主角比例横拉压扁发花。乙类的非整数豁免只覆盖**等比**缩放,不含纵横异倍变形。验收:机检〈立绘纵横倍率恒等〉
- **1920×1080 档策略**:全局等比(stretch=canvas_items+aspect **keep**)——16:10 设计在 16:9 窗口=等比 1.35×+左右黑边各 ~96px,构图与 1280×800 基准档完全一致;甲类 1.35 非整数缩放例外注记:轻度宽窄不均系本档位接受代价(像素游戏 shipped 常态),实机目检可感不适→回乙案(1920 独立 2× 基准)重议;乙类同前款。验收:目检〈1920 档构图与 1280 档一致〉
- **画布基准(暂定值)**:
- **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致);
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档沿「独立小源档」路线在样张定终值;
- **站桩立绘源档 = 八敌逐键定档**:主角 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 逐键档高校验〉
- **站桩立绘排版**:① 区高 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`〕,正式舞台背景插画 `stage/stage_bg.png` 到位后该层由背景绘制吸收,**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销;
- **站桩立绘源档 = 八敌逐键定档**:主角 48 不变;八敌源档高 = goblin 36 / giant_spider 38 / duergar 46 / hook_horror 58 / bugbear 62 / ogre_chief 74 / drider 90 / duergar_king 94,显示一律 **×2 整数档(NEAREST)** = 72–188。定档依据 = 生物体型三约束(钩爪恐魔>灰矮人、灰矮人<熊地精、食人魔>熊地精——体量=物种体型,非强度线性映射,见 character-design §5.2);可读性实测门槛 = 源高 ≥32(24 糊/32 可读/48 清楚/64 细节完整),全阶梯 36–94 达标;派生管线 = pixel-1.png 紧裁 → 等比缩至档高 → dom_block_resize 块内众数降采样(tools/pixelize_candidates.py `STANDEE_TIER_H`,`SIZES` 降格为管线上游中间分辨率表);体量存在感经整屏对比校验(B 区高读 `stage_h`/立绘显示上限 188)。验收:机检〈standee_asset_check 逐键档高校验〉
- **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:drider/duergar_king)**允许向上溢出盒顶 N px**(N 由实装批按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限由实装批用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
- **站桩石板地台件**:`scene_stage_plaza.png`,源 48×12、九宫格横向拉伸、顶缘受光线 2px 不随拉伸变形;跨三关恒定、不吃 §4.3 `wall_ambient` tint(独立理由见 §6.1 近景地台独立件条)。验收:机检〈地台件按九宫格拉伸后顶缘受光线宽度恒为 2px〉
### 8.3 导出格式与命名规范
@@ -515,7 +535,7 @@
### 8.4 帧数规格(样张冻结)
- **帧数规格(样张冻结)**:待机循环 4 帧;攻击/技能准备 6 帧单次;受击 2 帧;死亡 = 灰化+碎裂退场(§4.6.4 语义硬规则),序列帧归 ✦ 缺料清单;帧时长与结算节拍对齐(预告时序见 §4.6.4)。验收:机检〈序列帧数与档位一致〉
- **方向集**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟规则,样张确认。验收:目检〈四向帧齐、侧向镜像无破绽〉
- **方向集**:棋盘小人=**4 向**(下/上/侧,侧向左右镜像共用一份画稿);斜向移动/挖掘按**主导轴吸附最近正交向**呈现,不出 45° 斜向帧(斜向信息由位移轨迹与目标格预告承载,ITB 实态同款)。镜像与「旗面恒画右」纪律(character-design.md 冒险背包条)的相容归属:棋盘小人默认镜像豁免、旗右纪律限立绘侧——代拟口径已追认(棋盘小人朝左时旗面朝左,与立绘侧「旗面恒画右」并存;画稿维持一份 + 镜像,零新增资产)。验收:目检〈四向帧齐、侧向镜像无破绽〉
### 8.5 结构件完整性与参考核验
+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、唯一色数不低于 64。B 文字对比——战斗 C 区面板与明细带提示的 WCAG 不低于 4.5:1。C 明度三档——战斗屏信息面高于世界中,世界中高于世界底。D 文字禁压纹理——文字框背景中带灰度 std 不高于 18。F 文字在——菜单标题框内与中位灰度差大于 60 的像素占比落在 2% 到 55%。B/C/D 只在清单含 `shot_battle.png` 时跑,F 只在含 `shot_menu.png` 时跑,其余屏只跑 A。全过 0,缺参数或任一失败 1。
**已知坑与适用边界**:截图必须由窗口化 `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,不能据此判断主区健康。只核对路径存在与退役命名,不核对图像内容、尺寸或色板。
+223 -5
View File
@@ -1,8 +1,8 @@
# 生图提示词卡(角色级资产)v2.9
# 生图提示词卡(角色级资产)v2.12
> **性质**:美术清单生图通道(✦ 用户自跑,现走线上 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 现行」重生成(正序:B 路线),继承 seed 纪律 / 色相禁入带 / `#F2F2F2` 生成底色;沿革见 git 提交史。
> **边界**:本文件只管**角色级**资产的生图通道。图标/格面/样张走脚本管线(⚙ 代理出),规格在 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 提交史。
---
@@ -11,7 +11,7 @@
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`。这个目录只存候选,不进引擎。
3. **裁决**:你按 §4 验收速查预筛,挑出每一资产的入围候选拍板。
4. **后处理**:喊我一声,脚本管线代跑——去底转透明、最近邻降采样到目标尺寸、色板量化、四联底测试条(掩尘底/裸土底/暗底/灰度)。
4. **后处理**:喊我一声,脚本管线代跑——去底转透明、最近邻降采样到目标尺寸、色板量化、四联底测试条(掩尘底/裸土底/暗底/灰度)。后处理工具清单与用法见 [art-pipeline-tools](art-pipeline-tools.md)。
5. **转正**:终稿从 `artwork/candidates/` 转入切片资产目录,随表现层升级批接线;清单状态列改「已交付」。
**出图通道(2026-09-23 定稿)**:线上 API,用户自跑——本地 ComfyUI 生图路线已经用户拍板**彻底关闭**(母版工作流/像素 LoRA/Z-Image 模型已清退,不留复活预案,本卡不依赖任何本地管线)。
@@ -284,7 +284,225 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
后处理(程序侧,生成器脚本归档 zip 内〔archive 备选案-terrain-walls〕):周期对齐无缝裁剪→BOX 降采样 32×32→环绕混合→量化(12 色)→结构件合成;②③亦可由①程序加纹理级裂纹/浅斑替代(实证更稳)。
## 5. 明确不在本文件
## 5. 资产出图工单(四组九件)
> **范围注**:本文件原定位角色级(见文件头边界句),§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 缺口,不在本节填空。
> **验收标注纪律**:逐条按三档标 `←机检: <断言>` / `←目检: <判据>` / `←方向性`,照圣经头注 2026-10-06 用户拍板的写法。
> **流程**:出图前后一律走 §0 五步;出图登记义务照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,其余参数有则记),随候选图存 `artwork/candidates/<资产名>/`;登记不作正稿遴选前置。
> **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`scene_stage_plaza.png` 48×12、`ui_armor_light/ward/heavy.png` 24×24,路径见各组 ① 字段;登记=战斗一屏样张批,合并提交 3b66d1a6);组一背景插画已迁至 [battle-stage-prompts](battle-stage-prompts.md)(仍待出图),组四立绘重产仍待出图。
### 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`。本文件不再复写本件提示词,避免两处维护。
### 5.2 组二 · 近景地台件(1 件)
| 字段 | 值 |
|---|---|
| ① 路径与命名 | `game/assets/art/scene/scene_stage_plaza.png`——域键 `scene`(asset-path-map §3),命名照 §8.3。**跨三关恒定,文件名不带 board_type 后缀** |
| ② 尺寸档与像素网格 | 源 48×12;**九宫格横向拉伸**,顶缘受光线 2px 不随拉伸变形(§8.2 / §6.1 定值);高度维持源档 12px、横向拉至站桩宽。九宫格件不在甲/乙两分类内(§8.2 甲类 = 文本与语义件、乙类 = 角色与单位精灵),本件高度恒为整数像素、横向拉伸区只取纯色 |
| ③ 色板约束 | 进**战斗屏场景环境子板**(§4.1 分层②,不占 48 额度,余量 0 不动)。**零新增**——板身 `#968264`(STAND_STONE,§4.1 已转正进本子板)/ 顶缘受光线 `#C9AF83`(掩尘;2026-10-28 合并登记把地台顶缘受光线并进掩尘,并与 §8.2 高墙口沿高光 `Color8(196,172,132)` 统一为同一枚)/ 描边 `#4C4038`(2026-10-28 合并登记把地台描边并入此值)。单张像素域色数 ≤32。第二道门同本节 5.1 |
| ④ 明度带归属 | **世界中层 L 0.22–0.45**(§2)——判定对象 = 板身 `#968264`(实测 L≈0.234)与顶缘受光线 `#C9AF83`(实测 L≈0.449,材质受光属 §2 形体塑造允许域)。§6.1 把地台定死为世界中层承载面,就是本件的立法理由。描边 `#4C4038` 是结构墨线(§3.2 高墙墨线同族,职责 = 分隔),不按明度带判定 |
| ⑤ 是否程序化件 | 是 → **程序化直出即终稿,不须出图**。本件只有三块纯色 + 一条 2px 缘线,无材质纹理需求,按 §8.5「材质归模型、结构归程序」落脚本绘制即成立;它不属 §7.1 占位态 |
| ⑥ 验收判据 | 见下方逐条判据 |
| ⑦ 出图操作要点 | 见下方工艺要点(手绘/程序化,无须 AI 生图) |
**独立成件的两条理由(§6.1「近景地面与地台走独立件」条,写进工单备查)**:① 背景层按关卡出三张变体,地台若画进任一张就得三张各画一遍、且三关站位面会漂移;② 结算期明细带整条压住 B 区底带(带高 = `log_band_h_settle`,读布局表),地台在独立层才能跨关恒定并保持 §6.1 定死的世界中层承载面不变。
**逐条判据**
- ←机检: 九宫格横向拉伸后顶缘受光线宽度恒为 2px(§6.1 / §8.2 既有定值断言,本节不另立值)
- ←机检: 单张像素域色数 ≤32,且三个色值逐位等于 `#968264` / `#C9AF83` / `#4C4038`
- ←机检: 地台绘制点取常量、不进 §4.3 `wall_ambient` tint 槽(跨三关恒定)
- ←机检: 板身色相对亮度 L 落 [0.22, 0.45]
- ←目检: 横向拉到站桩全宽后端头与板身无纹理接缝、无错位;横向拉伸区零纹理
- ←方向性: 落影接地感成立(立绘双脚落石台上缘,§8.2 站桩排版条)
**工艺要点(⚙ 脚本绘制)**
1. 源 48×12 的十二行 = 1px 上描边 `#4C4038` + 2px 顶缘受光线 `#C9AF83` + 8px 板身 `#968264` + 1px 下描边 `#4C4038`;左右各 1px 描边,全周闭合。
2. 九宫格切片:顶缘 2px 受光线**整行落进上切片**(九宫格的上下边只横向缩放、纵向不缩放),左右切片取端头纯板身段——端头与板身同为纯色,切片取值无视觉后果,取多少都行;**顶缘线绝不能落进可纵向缩放的中间段**,否则拉伸后厚度会变。
3. 本件不参与 §6.1 的三关 tint:入包后绘制点直取 ③ 的三色常量。
### 5.3 组三 · 护甲类型图标三枚(3 件)
**三件共用字段**
| 字段 | 值 |
|---|---|
| ① 路径与命名 | `game/assets/ui/icons/ui_armor_light.png` / `ui_armor_ward.png` / `ui_armor_heavy.png`——域键 `ui`,落 `game/assets/ui/icons/`(与现役九枚状态图标同目录,asset-path-map §1);命名照 §8.3,对象键 = `armor`、变体 = 甲档键,与清单 §3.7 逐字一致 |
| ② 尺寸档与像素网格 | 源 24×24,显示 24px = 1×;九宫格 = 否;**甲类**(§8.2 甲类 = 文本与语义件,图标在列)→ 维持严格整数倍缩放硬规则,禁非整数缩放 |
| ③ 色板约束 | 进**语义/UI token 板**(§4.1 分层①),**占 48 额度——余量 0**(2026-10-28 用户裁:本组新增重甲冷灰 1 枚 + 5.4 组四新增卓尔浅紫灰皮 1 枚,全板余量 2 → 0,额度已用尽)。逐件取在册色(**素底明暗档已裁深档 `#282220`**,见 §5.5 缺口 2 已裁条):轻甲 = `WARM_FAINT #CDB88C`(深档下 8.08:1;原木色 `#8A5A34` 深档仅 2.72:1 不过门,按合并裁定改值、零额度)/ 魔法护甲 = 暖白 `#F0E2C8`(12.26:1,深档下本值不动)/ 重甲 = `#868C9A`(新入板,占 1 枚额度,深档下 4.65:1)。单张像素域色数 ≤32。第二道门同本节 5.1 |
| ④ 明度带归属 | **信息面层**(§2:信息面层 = 数字 / UI 数据 / 预告 / 语义标识)——图标是语义标识,与站桩名字行同排挂载。**信息面层 L 区间维持定性、不新增区间**(§5.5 缺口 3 核实结论:执法已被 §7.2 文字 ≥4.5:1 与 §4.1 图形 ≥3:1 两条相对判据覆盖,层间序关系由 §1.4 整屏②项断言,不需要绝对区间),本节落地判据走 §4.1 图形对比 ≥3:1 |
| ⑤ 是否程序化件 | 是 → **程序化产出即终稿**。来源照清单 §3.7:♻ 素材站通用护甲/符纹图标改色优先、⚙ 兜底程序化;♻ 件须过清单 §0 复用闸四道才入包 |
| ⑥ 验收判据 | 见下方共用验收 + 逐件验收 |
| ⑦ 出图操作要点 | 见逐件的来源与工艺要点 |
**共用验收(三件都要过)**
- ←机检: 源 24×24、显示 1× 整数倍(§8.2 显示档登记例外条:素材一字节禁改)
- ←机检: 文件名逐位等于 `ui_armor_light` / `ui_armor_ward` / `ui_armor_heavy`
- ←机检: 单张像素域色数 ≤32
- ←机检: 三枚对其实际挂载底色的图形对比 ≥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 同文断言)
- ←目检: 灰度下按甲面覆盖率三档可辨——无甲板 → 符纹带 → 大面积甲板(character-design §5.3 灰度通道条)
- ←目检: 三枚互辨,身份只靠形状不靠色相(§4.5 ⑨ 图标键族:色相不承载身份)
- ←方向性: 甲档不触 §5.1 剪影 archetype——不加肩甲外扩之类的轮廓改动,体量与凹凸计数仍由 archetype 独占承载
**5.3.1 轻甲 `ui_armor_light`(轻甲档)**
- 形状:甲面覆盖率 ≈ 0——一枚**皮条 / 布带**记号(斜跨的皮带扣或系带),主体留空,零甲板(character-design §5.3)
- 色:**`WARM_FAINT #CDB88C`**(2026-10-28 用户裁:素底取深档后原皮革木色 `#8A5A34`〔§7.2 木色〕仅 2.72:1 不过门,按 §4.1「不过门即换色合并、禁加色绕行」并入本值——**零额度**;代价 = 暖白族非木色族,已随裁定接受)
- **对比判据**(挂载底色 = 站桩名字行素底,2026-10-28 裁深档 `#282220`):**8.08:1 ✓**(≥3:1 图形门槛);对舞台底 `#3E362C` 6.12:1。木色族路线(新增木色亮档 `#C49A5E` 6.07:1)未取——须 +1 额度而余量已为 0
- ←机检: 本枚对其实际挂载深素底 ≥3:1(实测 8.08:1);入板后全板色对中三通道差全部 <8/255 的对数 = 0
- ←目检: 换色后本枚仍读作皮革/皮条,不与 5.3.2 符纹带、5.3.3 甲板在灰度下混读(character-design §5.3 灰度通道:三档按甲面覆盖率可辨)
- 来源与工艺:♻ 素材站 belt / strap 类图标改色,过清单 §0 复用闸四道(色板量化 / 禁入带吸色 / 灰度可辨 / 授权干净);⚙ 兜底按 24×24 网格手修——3px 带宽、1px 深墨描边,不画甲板
**5.3.2 魔法护甲 `ui_armor_ward`(魔法护甲档)**
- 形状:**符纹饰带**——一条走轮廓的发光符纹带,覆盖率居中,零金属甲板(character-design §5.3)
- 色:暖白 `#F0E2C8`(与轮廓描边同族,不占任何语义色,§5.3)
- 来源与工艺:♻ 素材站 rune / glyph band 类图标改色 → 闸四道;⚙ 兜底 24×24 内一条横向符纹带 + 两枚符点,符点不超 3×3
**5.3.3 重甲 `ui_armor_heavy`(重甲档)**
- 形状:**金属甲板**覆盖肩与胸,覆盖率三档中最大(character-design §5.3)
- 色:冷灰金属材质色 = **`#868C9A`**(2026-10-28 用户裁转定稿,见 §5.5 缺口 1 已裁条;新入 §4.1 分层①,占 1 枚额度)。**与 5.4.4 石君王岩石板甲是同一枚**——character-design §5.3 通道 = 甲面覆盖率 × 甲材质色,甲材质色按甲档统一,故石君王工单**直接引本枚 `#868C9A`、不另计额度**
- **对比判据**(挂载底色 = 站桩名字行素底,2026-10-28 裁深档 `#282220`):须同时满足 §5.4 ④ 立绘体块主色带 L 0.22–0.45 与本节 ≥3:1 —— 深档窗 = **[0.220, 0.450]**;本值 L≈0.261 落窗,**对深素底 4.65:1 ✓、对舞台底 3.52:1 ✓**
- ←机检: 本枚对其实际挂载深素底 ≥3:1(实测 4.65:1)且相对亮度 L 落 [0.220, 0.450](实测 0.261);入板后全板色对中三通道差全部 <8/255 的对数 = 0
- ←目检: 灰度下本枚甲板覆盖率三档最大,与 5.3.1 无甲板、5.3.2 符纹带可辨(character-design §5.3 灰度通道)
- ←方向性: 冷灰与全作暖底成材质对比(§5.3),不靠明度差与地台 `#968264`(L≈0.234)抢读——立绘与地台的可辨归 §5.1 轮廓通道
- 来源与工艺:♻ 素材站 plate armor 肩甲/胸甲类图标改色 → 闸四道;⚙ 兜底 24×24 内肩 + 胸两块甲板,与全作暖底成材质对比(§5.3 冷灰金属与暖底对比)
### 5.4 组四 · 主角与三 Boss 立绘重产(4 件)
**四件共用字段**
| 字段 | 值 |
|---|---|
| ① 路径与命名 | `game/assets/art/character/character_hero_portrait.png` + `game/assets/art/character/character_enemy_<enemy_id>.png`(域键 `character`,对象键 = `hero` / enemy_id,命名照 §8.3 与 asset-path-map §3 现役写法) |
| ② 尺寸档与像素网格 | **源档高逐键** = 主角 48 / ogre_chief 74 / drider 90 / duergar_king 94(§8.2 立绘源档表,逐件值见本节 5.4.1–5.4.4);**宽随源图宽高比,纵横倍率恒等**(§8.2 站桩立绘槽位 = 高定宽随条,禁把立绘塞进统一方框强拉);九宫格 = 否;显示一律 **×2 整数档** = 96 / 148 / 180 / 188;**乙类**(角色与单位精灵)——乙类允许非整数,本批按 §8.2 定案走 ×2 整数档 |
| ③ 色板约束 | 进**语义/UI token 板**(§4.1 分层①);**48 余量 0**(2026-10-28 用户裁:本组新增卓尔浅紫灰皮 1 枚,全板余量 2 → 0,额度已用尽)。逐件沿用 character-design §5.4 / §5.5 已登记色值;该两节只给形容词未给色值的部件,**2026-10-28 用户裁已落值**——四处换色合并(零额度)+ 卓尔浅紫灰皮新增 1 枚,逐件取值见本节 5.4.1–5.4.4,额度账见 §5.5 缺口 4。**石君王岩石板甲 = 重甲甲材质色、与 §5.3.3 同一枚,工单直接引 `#868C9A`、不另计额度**。单张像素域色数 ≤32。第二道门同本节 5.1 |
| ④ 明度带归属 | **世界中层 L 0.22–0.45**(§2 世界中层 = 格底顶面 / 角色 / 物件)——判定对象 = 角色体块主色。描边与落影底盘属轮廓通道(character-design §5.1)、紫赤点缀属 §4.2 色相独占的语义色,两者都不按明度带判定 |
| ⑤ 是否程序化件 | 否 → **须手绘**。口径 = **精细手绘像素**(2026-10-14 用户裁:仍是像素风,手绘像素簇 + 更高源密度;不是插画、不是 HD-2D、不回退高清图);**禁 AI 降采样当终稿**。现行 AI 降采样立绘 = 占位态,按 §7.1 占位完成条款须在终稿闸前换代 |
| ⑥ 验收判据 | 见下方共用验收 |
| ⑦ 出图操作要点 | 见下方工艺要点(手绘,不产生图提示词) |
**共用验收(四件都要过)**
- ←机检: 源档高逐位等于 48 / 74 / 90 / 94,显示 = 源 ×2(96 / 148 / 180 / 188),过滤 = NEAREST
- ←机检: 单张像素域色数 ≤32
- ←机检: 脚底基线对齐(派生管线四条:等比整数缩放 + 降色 ≤32 + 脚底基线对齐 + 命名合规)
- ←机检: 主角描边 = `(40,19,13)`;敌方描边取暖白族(非黑、非深褐)—— character-design §5.1 主角描边例外条
- ←机检: Alpha 直出——不透明区外缘 1px 环内无生成底残留(无 R/G/B 三通道均 ≥230 且 alpha > 0 的像素),防抠图白边
- ←机检: 零 HSV 300°–345° 像素(drider / duergar_king 两 Boss 除外,§4.2 独占)
- ←机检: 落影底盘在位(character-design §5.1 轮廓通道)
- ←目检: 放大 4 倍,轮廓与衣服是成组的像素,不是降采样噪点(三判据①)
- ←目检: 显示 96–188px 下脸、铲、旗、物种能认出来,不必先读名字(三判据②)
- ←目检: 显示仍用邻近采样,不出现插画那种平滑渐变(三判据③)
- ←方向性: 精细手绘像素气质——不是插画、也不是半像素半高清
三判据出处 = `reports/美术审计-cursor会话存档-2026-10-14.md` §2.2。
**工艺要点(手绘)**
1. 出件顺序 = 先主角、再三 Boss(Alpha 范围 = 主角 + 三 Boss 重产,其余敌种当前可读则保留到 Beta)。
2. **源密度判据 = 三判据②**:显示 96–188px 下脸、铲、旗、物种可认——照这条反推源档即可,逐键定档已完成(§8.2),本批不必再猜档位。
3. 手绘起稿顺序 = 剪影(§5.1 archetype)→ 体块主色 → 暗部与受光 → 描边 → 落影底盘。
4. 逐件 2–3 版候选;登记照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,手绘作者有则记),随候选图存 `artwork/candidates/<资产名>/`。
5. 候选评审 = 并排横评,**隐去文件名与来源后按三判据逐条判读**(§0 五步第 1 步「便于横评」的手绘侧落法),不靠记忆与印象拍板。
6. 出件 = 透明 PNG(Alpha 直出,禁先出白底图再抠);入包前过 §8.3 色板检查器(报新增色 / 近色告警 / 超 48 上限拦截)。
**5.4.1 主角(源 48 / 显示 96)**
- 形象 = character-design §5.4 形象锚逐字执行:蓝兜帽取下披肩、暖米白上衣、深棕工装裤、额前护目镜、半指手套、卷裤脚皮护膝、圆头挖掘铲、结构式冒险背包配朱红矩形旗、腰挂提灯
- 已登记色:兜帽 `#2E86DE` / 上衣 `#EFE3C8` / 长裤 `#6B4A2E` / 皮革 `#8A5A34` / 眼 `#33261B` / 旗 `#C63D22` / 描边 `(40,19,13)`
- 已落值(2026-10-28 用户裁):**铲刃浅铁灰 = `FOG_MEMORY_INK #C4C4C4`**;**提灯铜色 = `GOLD_DEEP #B07A14`**——两件均走换色合并、零额度(character-design §5.4 对这两件只有形容词,账见 §5.5 缺口 4)。代价已随裁定接受:`#C4C4C4` 一色两岗(语义岗 = 迷雾记忆墨 §6.5)、`#B07A14` 属金族(§4.2 金只说收益与兑现);两者均为角色随身件材质色,不进信息层与语义物件岗
- 破例记账:背包 + 旗 = 主角对 §5.1「对称」的唯一破例组,旗面朝向恒定画右;轮廓凹凸预算按背包 + 旗整组记账
- 朝向:战斗画面的侧身面向怪物形象属状态图(§5.4 生成顺序口径);重产沿用现役朝向,不重新裁定
**5.4.2 食人魔酋长 ogre_chief(源 74 / 显示 148)**
- 档位 = 精英档(形状仍守精英档:普通怪形 + 恰一处垂直延伸 = 头顶骨与羽冠,底盘不变;第一关关底 Boss 战,不升 Boss 形)
- 甲档 = 轻甲 → 甲面覆盖率 ≈ 0:皮条 / 骨饰点缀、裸身为主,零甲板零符纹
- 主色域 = 暖褐皮、圆肚;**暖褐皮 = `暖石 #B8A582`**(2026-10-28 用户裁:换色合并、零额度;体块主色带 §5.4 ④ L 0.22–0.45 校验 L≈0.387 ✓;character-design §5.5 该行原只有形容词)
**5.4.3 蛛化卓尔精灵 drider(源 90 / 显示 180)**
- 档位 = Boss(敌类中唯一允许锐角与不对称者,体量最大)
- 甲档 = 魔法护甲 → 符纹饰带(暖白,覆盖率居中);蛛腹几丁质属天然甲壳,不计甲档
- 独占:紫赤 `#8F2F6A`(色相 323°)+ 形状独占双通道;全项目色相 300°–345° 只许本件与 5.4.4 出现
- **卓尔上体浅紫灰皮 = `#A79CB8`**(2026-10-28 用户裁:**新增 1 枚**,入 §4.1 分层①——在册紫族四枚全部语义岗占用且 L <0.22,无一可复用)。体块主色带 L 0.22–0.45 校验 L≈0.355 ✓;色相 264° 避 300°–345° 禁入带,紫赤独占仍归 `#8F2F6A`。**对比实测(本节新算,此前未给值)**:对深素底 `#282220` **6.04:1**、对舞台底 `#3E362C` **4.57:1**——两处均过 §4.1 图形 ≥3:1 门槛
- 大体档:显示 180 > 150 → 允许向上溢出盒顶(§8.2 站桩排版③ 甲案;溢出量 N 由实装批用真图复算 x 区间定,血条仍落底)
**5.4.4 灰矮人石君王 duergar_king(源 94 / 显示 188)**
- 档位 = Boss(锐角与不对称,体量最大档)
- 甲档 = 重甲 → 金属甲板(冷灰,覆盖率最大)
- 独占:紫赤 `#8F2F6A` + 形状独占双通道;大地 / 岩石主题
- 岩石板甲与灰皮(2026-10-28 用户裁,character-design §5.5 该行原只有形容词):**岩石板甲 = 重甲甲材质色,与 §5.3.3 同一枚 → 直接引 `#868C9A`,不另计额度**;**灰皮 = `STONE #786E62`**(按**次级皮**口径——石君王重甲覆盖率最大、板甲视同体块主色,故灰皮属次级皮、不受 L 0.22–0.45 约束;换色合并、零额度)
- 大体档:显示 188 > 150 → 溢出盒顶同 5.4.3
### 5.5 缺口(四条 · 缺口 1/2/4 于 2026-10-28 已裁转指路,缺口 3 结论已立)
> **核销口径(依 art-bible v3.22 现行条款)**:四条逐条先过「§4.1 在册既有色能否换色合并」——不过门即换色合并、禁加色绕行(§4.1 色板增量纪律)。**48 色余量:裁定前 2 → 2026-10-28 裁定后 0(额度已用尽)**;本节给的是「哪几枚必须新增 / 哪几枚可合并 / 合并后余量 / 全新增要压缩哪几枚」的账,**不替作者拍板**(§4.1:额度不足时呈报作者,由作者决定压缩或合并既有色)。本节所举候选色值,**缺口 1 / 2 / 4 已随 2026-10-28 用户裁落定(见各条「已裁」注,本节只留裁定依据账并指路 §5.3 / §5.4)**;其余仍为候选,落定须过 §4.1 两道门(①§4.2 语义检查;②与既有任一色 RGB 三通道差不得全部 <8/255——宽松口径)。
1. **重甲冷灰金属材质色——已裁(2026-10-28)= `#868C9A`**,本节转指路(原「阻塞 5.3.3 上色」状态解除——出形与上色均不再阻塞;**与 5.4.4 石君王岩石板甲同一枚**——character-design §5.3 通道 = 甲面覆盖率 × 甲材质色,甲材质色按甲档统一,两处只占一枚额度)。character-design §5.3 只写「冷灰金属材质色」;**落法见 §5.3.3(本组载值)与 §5.4.4(直接引 §5.3.3 同一枚、不另计额度),色值账见 art-bible §4.1 本枚条目**。
- **换色合并逐枚核查——在册冷/中性灰全部不合格,无复用者**:`#3E4048` L≈0.052 / `#3F4147` L≈0.053(两者正在 §4.1 六对欠账清单内,清偿后只留一枚)/ `#3A3A3A` L≈0.042——三枚均越世界中层下沿 0.22;`#C4C4C4` L≈0.552 越上沿 0.45,且语义岗 = 迷雾记忆墨(§6.5);`#8A9AB0` L≈0.317 本落在带内,但色相 ≈215° 属蓝族,§5.4 语义约束 + §5.5「蓝族禁入敌方主色」拦;`#5A6C8C` 暮蓝为失败独占(§4.2);`#808080` L≈0.216 越下沿 0.004,且是引擎默认灰(§7.6 点名须接管,不宜作角色材质色)。
- **归属板**:进 §4.1 **分层① 语义/UI token 板,占 48 额度**。理由:甲材质色 = 材质色、跨屏恒定,不属分层② 环境色(环境色随场景走);5.3 与 5.4 两组工单第 ③ 字段均已登记为分层①。
- **裁定值(2026-10-28 用户裁,原候选值即本值,落定后转定稿)**:`#868C9A`(L≈0.261 / H≈222° / S≈0.13)。第二道门按宽松口径与在册色逐一比对,**无三通道全 <8 的对 → 过门**;§4.2 语义检查过门(不占红/金/蓝/紫赤/绿语义段,222° 不在 300°–345° 禁入带)。对比实测:对深素底 `#282220` 4.65:1、对舞台底 `#3E362C` 3.52:1、对浅素底 `#F5EAD3` 2.82:1(浅档已落选,不受该读数的门限约束)。
- **风险(已随 2026-10-28 裁定接受)**:本值与地台板身 `#968264`(L≈0.234)灰度差仅 0.028、对比 1.10:1。立绘与地台的可辨归 §5.1 轮廓通道(亮描边 + 落影)与冷/暖材质对比承载,不靠明度差——§5.3 明写「冷灰与全作暖底成材质对比」;§2 只在世界底层内部立了「同层内两底难辨」的子带纪律(该条立法理由明写为解决底层内部问题),世界中层无同款要求。
- ←机检: 本值入板后全板色对中三通道差全部 <8/255 的对数 = 0
2. **站桩名字行的落法 + 素底明暗档——已裁(2026-10-28)= 垫素底、素底取深档 `#282220`**,本节转指路(原记为「轻甲木色在深底上不过 3:1」;核出落法是它的前置,故先落这一条)。
- **推导核实 = 成立,且是既有条款的落法、非新条款**:站桩名字行是文字,§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` 亦在册。两档后果**相反**(下表留作裁定依据账):
| 受影响的位 | 浅档 `#F5EAD3` | 深档 `#282220` |
|---|---|---|
| 站桩名字行墨(现役暖白 `#F0E2C8`) | 1.08:1 ✗ → 按 §7.2 双色墨制「亮底转深墨」改主墨 `#33261B`(12.3:1) | 12.26:1 ✓ 不动 |
| 5.3.2 魔法甲暖白 `#F0E2C8` | 1.08:1 ✗ → 同须换深墨 | 12.26:1 ✓ 不动 |
| 5.3.1 轻甲木色 `#8A5A34` | 4.87:1 ✓ 不动 | 2.72:1 ✗ → 须换色合并(`WARM_FAINT #CDB88C` 8.08:1,零额度)或新增木色亮档(`#C49A5E` 6.07:1,占 1 枚) |
| 5.3.3 重甲冷灰 | 须 L ≤0.245,与 5.4 立绘体块主色带求交 → 窗 **[0.220, 0.245]**(候选 `#7E8492` L≈0.230,3.14:1) | 须 L ≥0.151 → 窗 **[0.220, 0.450]**(候选 `#868C9A` L≈0.261,4.65:1) |
- **裁定结论(2026-10-28 用户裁,取深档一列)**:① 站桩名字行墨(暖白 `#F0E2C8`)**不动**(12.26:1);② 5.3.2 魔法护甲暖白 `#F0E2C8` **不动**(12.26:1);③ 5.3.1 轻甲木色 → **接受换色合并 `WARM_FAINT #CDB88C`(8.08:1,零额度)**,不取新增木色亮档 `#C49A5E` 路线(须 +1 额度而余量为 0);④ 5.3.3 重甲冷灰 → 按深档窗 **[0.220, 0.450]** 落 **`#868C9A`**(4.65:1)。**素底值恒定 = `HP_BAR_BACK #282220`**(§7.4 血数垫牌同款,零新增)
- **推荐 = 深档 `#282220`**(四条理据)——**2026-10-28 用户采纳本推荐,裁定落深档**:① 与 §7.4 血数垫牌同款、既有素底,零新增;② 与 §4.1 在役亮档 token 同向——顺位徽标 `GOLD_BRIGHT` 对舞台底 6.47:1、魔法护甲徽标 `HERO_BLUE_BRIGHT` 5.88:1,现行徽标体系整体按暗底设计;③ 与 §7.4 v3.22 实测记录「暖白对舞台底 9.49:1 达标」不冲突,深档更暗则暖白更稳;④ 只动 5.3.1 一枚,名字行墨与 5.3.2 全部不动。**代价**:5.3.1 须换色(换色合并不需要额度,新增木色亮档要 1 枚)。**浅档的代价**:名字行墨与 5.3.2 两处换墨(零额度),但 5.3.3 被挤进 0.025 宽的窄窗,且与石君王灰皮(若亦须落 0.22–0.45)撞窗。
- ←机检: B 区站桩名字行与护甲图标正下方像素非地台板身色 `#968264`(§6.1 / §7.2 同文断言)
- ←目检: 名字行与三枚甲档图标同排挂载后,素底边缘与舞台底、地台轮廓不打架,不构成第二焦点(§3.4)
3. **信息面层 L 区间未定值**(5.3 明度带字段无区间可填):**结论 = 维持定性,不新增区间**——不为了与世界底层两子带对称而硬定一个区间。
- 理由一:**执法已被三处既有数值判据全覆盖**——文字 ≥4.5:1(§7.2)、图形 ≥3:1(§4.1 图标槽柔和档条)、数字墨/色牌的 L 界(§4.1:亮墨 L≥0.55 / 色牌 L≥0.70 / 深墨 L≤0.027)。三者都是「对其直接背景」的**相对判据**,信息面层成员恒压在具体底上,相对判据比绝对区间更贴执法面。
- 理由二:§1.4 整屏样张②项验的是**层间序关系**(信息面层 > 世界中层 > 世界底层),是相对断言,不需要信息面层的绝对上界。
- 理由三:世界底层拆两子带的立法理由 = 解决**同层内**两底(舞台底 L≈0.036 与已开格底 L≈0.10)灰度难辨(§2 该条明写),信息面层**无同款实证问题**(成员尺寸与位置差异大,且有墨/牌强对比),无对称立法理由。
- 理由四:硬定上界会误伤在册高亮档——`#F5EAD3` 色牌 L≈0.83、纯白 L=1.0 都在信息面层内,上界定死(如 0.90)会把它们拦在层外。
- **机检断言写得出来(证明不定值也能执法)**:←机检: 屏内信息面层取样点 L > 同屏世界中层取样点 L > 同屏世界底层取样点 L(§1.4②项同口径);←机检: 信息面层成员对其直接背景,文字位 ≥4.5:1、图形位 ≥3:1(§7.2 / §4.1)
4. **五处角色部件色值——2026-10-28 用户裁已落值**(不阻塞开工;手绘口径下必须落值,现已落)。**逐件取值见 §5.4.1(铲刃 / 提灯)、5.4.2(食人魔皮)、5.4.3(卓尔皮)、5.4.4(石君王灰皮)**;下表留作裁定依据账,色值已逐件落定(见上),不再以「待裁」表述:
| 部件 | §5.4 ④ 明度带归属 | 换色合并候选(在册,零额度)**——2026-10-28 裁定采纳** | 合并代价 / 淘汰理由 | 新增候选(**未取**,裁定走合并路线) | 第二道门 |
|---|---|---|---|---|---|
| 主角铲刃浅铁灰 | 手持件,不属「体块主色」判定面 | `FOG_MEMORY_INK #C4C4C4`(L≈0.552,中性灰 S=0) | 一色两岗(语义岗 = 迷雾记忆墨 §6.5,与 §4.1「常量本尊不动」先例相左);若后续纳入明度带判定则越上沿 0.45 | `#A8AEB8`(L≈0.421 / H≈218° / S≈0.09) | 过门(无三通道全 <8 的对);非语义段 |
| 主角提灯铜色 | 同上 | `GOLD_DEEP #B07A14`(L≈0.232 / H≈39°,落世界中层) | 金族语义岗(§4.2 金只说收益与兑现、禁作日常强调防通胀);与皮革木色 `#8A5A34`(L≈0.130)明度差 0.10,可辨 | `#C08A3A`(L≈0.297 / H≈36°) | 过门 |
| 食人魔酋长暖褐皮 | **体块主色 → 须落 L 0.22–0.45** | `暖石 #B8A582`(L≈0.387 / H≈39°,在册于 theme README 溯源表) | 一色两岗(该值现为滑轨槽字面量);已排除 `#968264`(地台承载面 §6.1)、`#B39A78`/`#C9AF83`(格底语义,§5.1 剪影禁借格底成立)、`#8A5A34`(L 0.130 越下沿) | `#B08050`(L≈0.253)/ `#A97C4E`(L≈0.234) | 过门 |
| 卓尔精灵浅紫灰皮 | **体块主色 → 须落 L 0.22–0.45** | **无可复用**:在册紫族四枚全部语义岗占用且 L <0.22——`#7C3AA8` 封印紫(退役)、`#7A4294` 妨害紫(§4.6.1 语义)、`#8A72C4` / `#7B5BD6` 类型微光族(§4.2 分域纪律:微光色相禁迁移复用为物件/阵营/UI 语义色) | — | `#A79CB8`(L≈0.355 / H≈264° / S≈0.15;避 300°–345°,紫赤独占仍归 `#8F2F6A`) | 过门 |
| 石君王岩石板甲 | 与缺口 1 同一枚,**不另计额度** | 见缺口 1 | 见缺口 1 | 见缺口 1 | 见缺口 1 |
| 石君王灰皮 | **口径已裁(2026-10-28)= 次级皮**:石君王重甲覆盖率最大(§5.3),板甲视同体块主色 → 灰皮属次级皮、不受 0.22–0.45 约束 | 不受约束口径下可合 `STONE #786E62`(L≈0.160 / S≈0.18 近中性暖灰) | 若按体块主色口径则 L 越下沿 0.06,不可合 | `#9A968C`(L≈0.306)/ `#949089`(L≈0.281 / S≈0.07) | 过门;两候选与地台 `#968264` 灰度差 ≥0.047 |
**额度账(48 余量:裁定前 2 → 2026-10-28 裁定后 0,额度已用尽)**
- **必须新增 = 2 枚(已裁落定)**:① 重甲冷灰金属材质色(缺口 1,在册冷灰全部不合格)= **`#868C9A`**;② 卓尔浅紫灰皮(在册紫族全占岗且过暗)= **`#A79CB8`**。→ 余量 2 → **0**,额度用尽;后续新增须先压缩或合并既有色(压缩哪几枚由作者裁)。
- **可合并 = 5 处,零额度(已裁接受)**:铲刃 → `FOG_MEMORY_INK #C4C4C4`;提灯 → `GOLD_DEEP #B07A14`;食人魔皮 → `暖石 #B8A582`;石君王灰皮 → `STONE #786E62`(按**次级皮**口径);轻甲木色 → `WARM_FAINT #CDB88C`。
- **浮动一枚 = 5.3.1 轻甲木色(已裁)**:深档素底下**接受换色合并 `WARM_FAINT #CDB88C`**,零额度,余量不击穿;新增木色亮档 `#C49A5E` 路线未取。
- (原「若作者选全部新增(7 枚)」一支,留作依据账):击穿 5 枚。压缩只能从既有色里腾——优先合「宽松口径下仍未清偿的在册近色对」,每合一对腾 1 枚。机检扫到的**活值**近色对(须先剔除退役值与场景环境子板成员再计数;本清单未逐枚核语义岗,合哪几对由作者裁):`GOLD_BRIGHT #E0B860 ↔ GOLD_LIT #E6BE5A`(差 6/6/6);`WARM #F0E2C8 ↔ MAP_BAR_PLATE #F0E4C8`(差 0/2/0);`RAP_DOT_CORE_WARM #F8E2A8 ↔ INK_L_GOLD #F8E3A8`(差 0/1/0);`DIGIT_PLATE #F5EAD3 ↔ CAMP_ROW_PLATE #F0E6CE`(差 5/4/5)。**注:这四对均不在 §4.1 已列六对欠账清单内**,属该清单未扫到的部分——按 §4.1 现有断言「全板色对中三通道差全部 <8/255 的对数 = 0」,它们同样在清偿范围内;是否随本批一并清偿由作者裁。
- **本节不替作者决定压缩或合并哪几枚既有色**。
- ←机检: 本节候选值落定后,全板色对中三通道差全部 <8/255 的对数 = 0,且板内色数 ≤48
- ←目检: 五处部件色上稿后,灰度下铲 / 提灯 / 皮 / 甲互相可辨,不靠色相单独承载(§5.1 轮廓通道;§4.5 色弱清单)
- ←方向性: 五处部件色服务于「随身件画得出来、认得出」(character-design §5.4 秘宝形象候选池条),不抢体块主色的读序
---
## 6. 明确不在本文件
- **意图准备动画三档、战斗演出类**:动画序列帧,等一屏样张锁动画方案后另立规格卡。
- **格面徽章 19 枚 / 关卡节点图图标 / 砖面终稿 / 地形变异纹带**:脚本管线,规格锚 art-bible §4.6(图标键契约)、§1.4(样张验收)与 §3.2,不需要生成提示词。
+1
View File
@@ -40,6 +40,7 @@
| 连锁坍塌显形演出 | ⬜ | [art-assets-checklist](art-assets-checklist.md) §4 |
| 封印特效(动作=封印,两件:施印特效+印记常显) | ⬜ | [art-bible](art-bible.md) §3.2 + [art-assets-checklist](art-assets-checklist.md) §4 |
| 弹道 / 退场等动画密度 | ◐ 程序化弹道在役(play_projectile 0.16s+0.19s 到达拍调度;密度终稿归战斗表现) | [art-assets-checklist](art-assets-checklist.md) §4(战斗表现输入清单) |
| B 区舞台背景三关变体+共用连续地面层(场景件) | ✅ 已交付(2026-10-31 入包 `game/assets/art/scene/`:三张背景 1280×252、共用地面 1280×100;地面按作者裁保留候选 v12b 原亮度 L≈0.132) | [art-bible](art-bible.md) §6.1 · [battle-stage-prompts](battle-stage-prompts.md) §2 |
---
+1 -1
View File
@@ -27,7 +27,7 @@
|---|---|---|
| `character/` | 角色棋盘帧 + B 区立绘 | character_hero_idle/walk(棋盘 32×32 朝左基准)、character_hero_portrait(B 区立绘 46×48)+ character_enemy_<键> 八敌 |
| `fx/` | 特效帧 | shovel_swing_f0/f1/f2(挥铲三帧,握把锚 (18,12)) |
| `scene/` | **场景图**(背景层 + 可点物件层;营地场景图首批) | `scene_camp_bg`(背景)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件,不可点)——登记超前:营地场景图批落地前盘上未建 |
| `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 已入包;登记超前:营地场景图批落地前盘上未建 |
| `board/` | 砖面/封印印记(三关换皮 + 封印印记件) | terrain_floor_<board_type>_<n>.png(三关地面系)、seal_mark.png |
## 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、脚本管线与实装批。
+50 -26
View File
@@ -1,9 +1,9 @@
# 营地场景 · 分层生图提示词卡 v1.0
# 营地场景 · 分层生图提示词卡 v1.2
> **性质**:营地场景图(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 为准。
> **边界**:只管营地场景图的分层出图。角色、图标、格面走 art-prompts.md 与脚本管线,不需要提示词。
> **建立**:2026-10-25 · 状态:九条提示词成文,出图未开始(出图通道由作者自定)
> **建立**:2026-10-25 · 状态:十条提示词成文(背景层 1 + 围墙层 1 + 八个物件层),出图通道由作者自定
---
@@ -11,10 +11,10 @@
1. **出图**:按下面各条把提示词贴进生图渠道。出图前先放**风格参考图**(见 §1)。
2. **入库**:候选原图放 `artwork/candidates/<资产名>/`,文件名 `v1.png / v2.png / v3.png`。
3. **后处理**:脚本管线代跑——去底转透明、降采样到终稿尺寸(整数倍)、色板量化、四联底测试条。
3. **后处理**:脚本管线代跑——去底转透明、降采样到终稿尺寸(整数倍)、色板量化、四联底测试条。**背景层不去底(全幅不透明)**;围墙层与八个物件层去底转透明。后处理工具清单与用法见 [art-pipeline-tools](art-pipeline-tools.md)。
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 倍,保证整数倍降采样)
**画什么**:天空、远山、地面、木围墙、门柱与门洞
**不画什么**:七个物件、门扇、角色、接地阴影——那些是上面的独立图层
**画什么**:天空、远山、整片地面、门洞位置一条通向远处的小路
**不画什么**:木围墙、门柱、门扇、七个物件、角色、接地阴影——围墙与门柱是 §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 |
|---|---|---|
| 顶栏 HUD 保留带 | y ≤ 48 | **y ≤ 96**(保持空旷) |
| 围墙带 | y 267–310 | **y 534–620** |
| 门洞(必须留空) | x 520–760 | **x 1040–1520** |
| 门柱 | x 500–520 / 760–780 | **x 1000–1040 / 1520–1560** |
| 背景层 `scene_camp_bg` | 全幅 1280×800 | 全幅 2560×1600 |
| 围墙带(wall 层桩身) | y 267–310(**高 43px**) | y 534–620(**高 86px**) |
| 门柱(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,16 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
### 逐件正文(只替换这一段)
| 件 | 层 | 终稿矩形 | 出图画布 | 正文 |
| 件 | z 序 | 终稿矩形 | 出图画布 | 正文 |
|---|---|---|---|---|
| 布告板 | L1 | 130×140 | 260×280 | §3.1 |
| 门扇 | L2 | 240×220 | 480×440 | §3.2 |
| 木桩 | L3 | 120×180 | 240×360 | §3.3 |
| 锻炉·铁砧 | L4 | 240×240 | 480×480 | §3.4 |
| 帐篷 | L5(不可点) | 300×170 | 600×340 | §3.5 |
| 宝箱 | L6 | 130×90 | 260×180 | §3.6 |
| 笔记本 | L7 | 100×80 | 200×160 | §3.7 |
| 篝火 | L8 | 160×160 | 320×320 | §3.8 |
| 布告板 | 2 | 130×140 | 260×280 | §3.1 |
| 门扇 | 3 | 240×220 | 480×440 | §3.2 |
| 木桩 | 4 | 120×180 | 240×360 | §3.3 |
| 锻炉·铁砧 | 4 | 240×240 | 480×480 | §3.4 |
| 帐篷 | 4(不可点) | 300×170 | 600×340 | §3.5 |
| 宝箱 | 5 | 130×90 | 260×180 | §3.6 |
| 笔记本 | 5 | 100×80 | 200×160 | §3.7 |
| 篝火 | 6 | 160×160 | 320×320 | §3.8 |
**3.1 布告板(→ 图鉴)**
```
@@ -133,10 +149,18 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
## 4. 验收(机检 + 目检)
**背景层**
**背景层 `scene_camp_bg`**
- 黄度 =(R+G)/2−B 分区取样:天空 ≤20、远山 ≤20、地面 ≤35
- 冷色像素占比 ≥55%;本层不应出现任何暖色高饱和像素
- 目检:顶部约 1/16 完全空旷;无地平线;围墙密排尖桩、桩顶削尖、不透缝;门洞空着没有门扇
- 目检:顶部约 1/16 完全空旷;**无地平线、无远景地面带**(远山自然渐隐过渡到地面);不含围墙、门柱、门扇、物件、角色
- 目检/机检:门洞 x1040–1520 处**有一条清晰可辨、向远处收窄的土路**(非纯色块),透出「出去即冒险」
- 机检/目检:全幅**无任何独立物件/图标/徽章/标记**(前景地面除小路外全空);参考做法=中下方块区独立色数应≈邻区(旧口径 v1 该区仅 179 色)
**围墙层 `scene_camp_wall`**
- 透明底 `#F2F2F2` 可去底、奶油描边闭合
- **桩身高度 = 出图 y534–620(终稿 43px);门柱略高 y480–620。桩身超出围墙带下沿 y620 达 5px 以上即判红**
- 目检:密排竖木桩、桩顶削尖、不透缝、无横木;门洞 x1040–1520 **完全透明**(透出 bg 小路)、无门扇
- 单张色数 ≤32(量化后)、缩放为整数倍无拉伸痕迹
**八件**
- 单件居中、四周留边、整件完整不出画
@@ -150,7 +174,7 @@ A campfire made of a ring of grey stones around crossed stacked logs with a warm
## 5. 待裁(不阻塞出图,阻塞坐标表与定稿)
1. **帐篷尺寸**:现 300×170 待重定,重定后按比例重出
2. **门扇高宽**:现 240×220,围墙带只有 43px 高,比例待裁
2. **门扇高宽**:现 240×220,围墙带只有 43px 高;围墙独立成层后门扇叠在门洞上,两者高宽关系待裁
3. **布告板位置**:现 y330 悬空在围墙下方,建议 `(845, 268, 130, 140)` 挂回围墙带
4. **描边颜色追认**:本卡按奶油色(去底技术前提);若改参照物的深灰褐,需换去底方案
5. **营地环境子板色值**:art-bible §4.1 为数值单点,色值待作者拍板,本卡不复述
+2 -2
View File
@@ -24,11 +24,11 @@
- **体量档位 = 生物体型通道(原「强度先验·普通 < 精英 < Boss 单调递增」读法已弃用)**:体量按**物种天性**定,禁按强度档位线性映射——小体型精英与大体型普通怪并存(锁定实例:钩爪恐魔 > 灰矮人、灰矮人 < 熊地精、食人魔酋长 > 熊地精);「大个子多半有重击」的强度先验读法改由意图徽标(§5.6)与甲档(§5.3)承担,体量不再报强度;图像不报数,精确数值只在结算明细。
- 像素画布/精灵尺寸**不在本节**——归圣经 §8 资产规范;本节只锁比例关系与档位次序。
## 5.3 护甲类型视觉区分(候选方案,样张冻结终稿)
## 5.3 护甲类型视觉区分
> **白话**:敌方有三种护甲类型——轻甲(light)/魔法护甲(ward)/重甲(heavy),克制博弈需要玩家从画面读出"这只怪穿什么甲";本节定三档甲怎么画。键与命名事实源 = `data/schemas/enemy_table.gd` 的 `armor_type` 枚举与 [术语表](../gdd/术语表.md)(轻甲/魔法护甲/重甲)。
- **通道 = 甲面覆盖率 × 甲材质色**,不触 §5.1 剪影 archetype(底盘与体量轴不动)——「重甲语义走色板不作形状独占」的落法:甲档不新增独占通道(独占 = Boss 双通道专属),装备配件画法不属独占。
- **通道 = 甲面覆盖率 × 甲材质色**,不触 §5.1 剪影 archetype(底盘与体量轴不动)——「重甲语义走色板不作形状独占」的落法:甲档不新增独占通道(独占 = Boss 双通道专属),装备配件画法不属独占。**本节已定稿**(候选标注注销):三档画法逐字按本节执行,甲档不触 §5.1 剪影 archetype——不加肩甲外扩之类的轮廓改动,体量与凹凸计数仍由 §5.1 archetype 独占承载。
- **轻甲 light**:甲面覆盖率≈0——皮条/布带点缀或裸露,体表以物种本色为主。
- **魔法护甲 ward**:甲面覆**符纹饰带**——发光符纹走轮廓描边同族暖白,不占任何语义色(圣经 §4.2),覆盖率居中。
- **重甲 heavy**:**金属甲板**覆盖肩/胸/盾沿,冷灰金属材质色(与全作暖底成材质对比),覆盖率最大。
+1 -1
View File
@@ -2,7 +2,7 @@
> **渠道定位**:生图渠道 = **远程对话式生图为主,本地 ComfyUI 为备选**;本文件是**对话操作单**(怎么开对话/贴什么参考图/怎么续写),提示词正文词库归 [art-prompts](art-prompts.md)——分工:词库改词库、操作单改流程,同一段正文不双处维护。
> **冲突优先级**:画法与色彩以 [art-bible](art-bible.md) 为准,人物规格以 [character-design](character-design.md) 为准;本单为执行侧操作单,不另立规格。
> **出图登记义务(复现性)**:每张产出登记 **模型名 + 分辨率 + seed(可取则取)+ 对话/日期**,随候选原图同存 `artwork/candidates/<资产名>/`;缺登记的候选不入正稿遴选。
> **出图登记义务(出处备查)**:每张产出随图注明 **用途**(备选/参考/其他什么用)与 **生成时间**(YYYY-MM-DD HH:MM 格式),随候选原图同存 `artwork/candidates/<资产名>/`;模型名、seed、分辨率等参数有则随手记、无则不填,分辨率这类文件属性里自带的值不必抄。登记不作正稿遴选前置——缺注明不落选,遴选时问清用途即可。
> 开新对话出图:同一角色 2–3 版候选各开新对话(同对话续写=修图);同角色换姿势在同对话续写。分辨率开大档;候选原图存 `artwork/candidates/<资产名>/`。
## 1. 主角战斗立绘
+35 -23
View File
@@ -2,8 +2,8 @@
## 文档状态
- 版本:1.22
- 最近更新:2026-10-28(迭代 40 拆件批反哺落文——编排域拆件定稿节转现状登记:reward_economy 消费依赖改注入 Callable 口径、BattleFxHost 补 luck_bias_for 查询口、接线款补注入 Callable 款与 battle_hooks 三键回调束形态、补 RunLedger 退役后战斗域读面款、S13 豁免登记补第三条 battle_orchestrator 类变量键)
- 版本:1.26
- 最近更新:2026-10-30(迭代 42 反哺点落文——ShopSession 行对外暴露格按委托壳拆除后实测直连面重写:完整直连方法清单+set_shop_pool 调用面改述;沿革查 git)
- 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准)
@@ -85,15 +85,15 @@
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|---|---|---|---|---|
| **ConfigDB**(autoload,D2) | 全部玩法数值表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机。**2026-10-27 全清设计定稿:行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面(≤20);表持有成员按域收进域容器 Resource(分组建档于实现卡,消费方改址随该批)**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);`load_tables(root, collect_only)`;诊断门面;`set_reload_gate()`;`reload` | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,2026-10-27 全清设计定稿——实现批建件) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族) | 槽位读写原语族+摘要投影(SaveManager 与菜单消费方直连) | save_manager(注入路径与模板配置);FileAccess——LOW |
| **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,2026-10-27 全清设计定稿——实现批建件) | 存档内元进度数据的读写唯一层(自 save_manager 收编 accessor 族:钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 数据读写原语族+只读投影(MetaProgression 业务层与营地/锻造 UI 消费方直连;meta 的纯转发读方法随件退役) | save_manager(序列化接线);FileAccess——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 |
| **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,已建件——迭代 41 存量债批) | 存档内元进度数据的读写唯一层(自 save_manager 收编 accessor 族:钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 写口原语 13+只读投影 6(tree_state/forge_state/attune_state/career_state 四投影+keystone_balance/item_unlocked)=公开 19(add_keystone 私有 `_add_keystone`),零 _data 副本零缓存;MetaProgression 业务层与营地/锻造 UI 消费方经 `save.meta_state.` 直连(meta 的纯转发读方法已退役) | save_manager(序列化接线);FileAccess——LOW |
| **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 |
| **action_economy**(A3) | AP/RAP 账本、last_round_end、轮次相位机 {Idle, Active, Waiting}、结束仲裁 | 意图与通知布尔面:notify_round_start / request_dig / request_stop / request_refresh / notify_settlement_done / notify_combat_end;预授权通道:grant_reserve_ap / finish_round_stop;ap/rap/phase 只读 | ConfigDB(ActionEconomyTable);effect_engine(事件发射 dispatch——基础层内同层引用);B1 回发 next_round_mode/battle_over | 无 |
| **variation 定义**(A4) | VariationTable schema(现行 6 行:六 region——贫矿脉 global 行 2026-10-16 用户令屏蔽删行,global 型机制代码保留)与 F1 候选池门控谓词 | `variant_pool(level_index, board_type, node_type="")`(门控三维:unlock+board_types+节点身份——global 行仅 boss/elite 入池,缺省跳过零扰动);`global_variant_row(level_index, board_type, node_type)`(全局型单行决议访问器) | ConfigDB(VariationTable) | 无 |
| **status_container**(B3) | 状态实例容器(战斗域生命周期、跨轮跨图延续) | 5 API:notify_battle_start / apply_effect / query_active / decay_round / notify_battle_end | ConfigDB(StatusEffectTable) | 无(RefCounted) |
| **effect_engine**(内容数据驱动扩展批 iter17 建域) | 零运行时可变状态的系统(见「架构原则」条 8):全同步解释器——事件由宿主在明确代码点位显式调 dispatch 进件,命中序=装载声明序(feats→treasure→树规则)写死保种子复放,不是事件总线/事件循环;事件/事实/效果类型/算子四词表封闭 + 树规则合成订阅六键(posture 每关首轮窗/sense 两组限次 counter 闸·adv_ 前缀走冒险桶跨战斗持久) | rebuild(每战重建订阅表)/`dispatch(ctx, event, payload, facts)`/`validate_trigger_row` + 静态计数口 `counter`·`counter_add`·`counter_spend`·`bucket_of`·`counter_id_legal`(计数器读写只经 EffectContext 引用束,无导出/灌入接口) | ConfigDB(feats/treasure 行集)、EffectContext(四桶引用由宿主持有:round/battle 桶归编排器、level 桶归关卡域、adventure 桶归应用层——RunLedger 已随迭代 39 拆件批退役,桶字典由 adventure_runner 持有) | 无(零随机全确定) |
| **save_system**(D3,Alpha;iter27 已升格 autoload「SaveManager」) | 存档格式与版本比较 | (契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SCHEMA_VERSION + master_seed + 各流 scope 计数器快照,须在无正在执行中的 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW |
| **save_system**(D3,Alpha;iter27 已升格 autoload「SaveManager」) | 存档格式与版本比较 | (契约规范源:`../../gdd/systems/表现与支撑/存档序列化.md`——契约锚:SAVE_FORMAT_VERSION + master_seed + 各流 scope 计数器快照,须在无正在执行中的 open_stream 实例时切出) | ConfigDB、RngService 只读计数器 | FileAccess——LOW |
| **settings/localization**(D6,愿景) | 设置项、翻译数据 | (契约规范源:`../../gdd/systems/表现与支撑/设置与本地化.md`) | ConfigDB | 无 |
### 核心层
@@ -101,29 +101,33 @@
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|---|---|---|---|---|
| **board_generator**(A1) | 生成算法(五阶段管线)、BoardGenTable 回填、生成期指标集、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量(metric_version);生成姿态 = MVP 同步生成 + 加载幕(切片实证 10×10/12×12 同步可接受,WorkerThread 留扩展) | `generate(L, D, rng) -> Board`;度量独立入口×3(推理器 / F3 度量 / 合法性谓词——独立纯函数、同一实现) | ConfigDB(BoardGenTable/VariationTable);board_gen 流(一次 open_stream = 一张图);dig_ap_cost 只读 | 无 |
| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 封印集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);查询只读面(current_board / hero_pos / is_revealed / has_seal 等);`compute_move_path(cell)` 只读;`retire_board()`;三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举 | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end / 相位 | 无(零表读取、零 UI——切片实证) |
| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 封印集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);查询只读面(current_board / hero_pos / is_revealed / has_seal 等);`compute_move_path(cell)` 只读;`retire_board()`;三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举 | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end(迭代 41 由属性改方法)/ 相位 | 无(零表读取、零 UI——切片实证) |
| **unit_container**(B2) | hero/enemy 单位实例(hp/atk/实例池:池快照+冷却+摇定动作+保底计数)、行值快照 | spawn / query_alive / rollable_skills / special_skills / check_trigger_unlocks / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / check_skill_unlocks / despawn_all(2026-09-16 敌人技能池批:query_beat/query_attack/advance 随行为环退役;rollable_skills/special_skills/check_trigger_unlocks 带 facts 参——2026-10-27 情境条件触发器实装,事实缺键=条件假拦下,百分比事实走阈值式折算) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 |
| **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影 | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / phase / weighted_pick;另有 is_mapped 静默布尔投影(批量工具用) | ConfigDB(RewardTable);BattleFxHost 经注入 Callable `luck_bias_query` 取偏运合计(reward_roll 发射与 bias_rewards 求和移至效果层完成);reward_roll 流(scope = 每次 install 批) | 无 |
| **helper_items**(A5,切片已实装秘宝域) | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 |
| **CurioDeck**(`core/curio_deck.gd`,A5 秘宝域令牌池件,迭代 37 自 AdventureRunner 拆出) | 三选一/商店共享池的持久多重集、抽签(候选抽取)、cap 库存与专长凭证三计数与判定唯一所有者——件只管池与判定,停点编排与兑付推进留冒险编排器 | `draw_candidates(channel)` 抽签;`consume_deck_token` / `consume_pool_purchase` 池消耗;`cap_ok` 上限判定;`feat_ticket_consider` 凭证判定;`stock_snapshot` / `pool_stock` 只读投影;商店购买消费经注入 Callable 链至 CurioStop 兑付单点(迭代 39 起转纯池——bind_handlers 反向回调已退役) | ConfigDB(秘宝令牌行集);注入 RngServiceCore(reward_pick 封闭流,禁自建随机) | 无(RefCounted 纯逻辑) |
| **ShopSession**(`core/shop_session.gd`,商店会话件,迭代 37 自 LevelRunner 拆出) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面由 LevelRunner 同签名委托承载(外部零变化) | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 |
| **ShopSession**(`core/shop_session.gd`,商店会话件,迭代 37 自 LevelRunner 拆出) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面=调用方经宿主 LevelRunner 公开成员 `shop` 直连(调用直连不留委托壳,2026-10-26 Middle Man 裁定;迭代 42 拆除),直连消费的方法=`shop_products`/`current_price`/`purchased_tier`/`buy_product`/`player_request_refresh`/`set_shop_pool`/`pool_price`/`shop_shelf`/`shop_refresh_free_left`/`shop_refresh_cost`/`shop_refresh`/`spend_gp`/`buy_pool_item`/`is_shop_pool`/`shop_bp_shelf`/`shop_bp_visit_buys`(另白盒两入口仅测试侧:`_draw_shelf`/`_spend_discounted`);set_shop_pool 调用面=pool 注入经 LevelRunner 构造 ctx 供给(构造期内部直调,原委托块注释「AdventureRunner._start_current_level 唯一调用方」系陈旧表述、随壳删除作废),测试面直连 `shop.set_shop_pool` | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 |
| **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(同调行投放面);注入战斗使用路径 | 无 |
| **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`,冒险域·断点组装与回放件,已建件——迭代 45) | 存档断点组装与回放唯一承载(自 adventure_runner 收编 6 成员+函数族):断点快照拼盘、回放回填、读侧策略分发;`BREAKPOINT_FIELDS` 27 键登记单源随件(键名/策略标签/键序冻结) | 构造=引用束注入(九引用束成员解包为原名成员——保迁移函数体逐字);`resume_adventure` 留宿主(壳+宿主侧回填语句——R2 所有权缝分家);断点键面零变化;实测类变量 15(贴顶禁再增) | adventure_runner(宿主);注入主角账三件与 curio/stop/bank/loadout 引用 | 无(RefCounted 纯逻辑) |
### 功能层
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|---|---|---|---|---|
| **battle_orchestrator**(B1) | 段序骨架(七段结构冻结)、轮 facts 缓冲、可回放步骤流、[明细行协议](../../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)(基础行型+扩展行两层 + 字段分层,词表以 owner「明细流水」节为准)、条款③⑤闭合对账、**战斗域全部跨系统调用权**、效果引擎宿主面与树规则挂点施加面(**已拆出 BattleFxHost 并实装(2026-10-28 迭代 40——battle_fx_host.gd/reward_grant.gd 两件在库,效果引擎宿主面归 BattleFxHost 行承载,拆件细则见「战斗编排域拆件定稿」节)**) | `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——效果引擎全同步解释器) |
| **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 引用束与编排回调) | 无 |
| **BoardInstaller**(`core/board_installer.gd`,B1 域·装板域件,2026-10-29 尾波清偿批设计闭合——实现批建件,细则见「战斗编排域拆件定稿」节轴丙) | 棋盘安装与变异布置域唯一承载(自 battle_orchestrator 收编装板族六函数):生成受理、全局作用域变异决议、资源摇号资格域组装(roll_board 恰一次)、region 变异决议与连通片布置;BoardGenerator 实例随件自建自接线(宿主成员退役)——宿主侧装板后状态复位(封印/完美计数清零、板序推进、board_changed 发射、秘宝叠加层退役)留宿主薄壳,时序逐字保持 | `install(during_round) -> bool` 单入口;构造=引用束注入(rng/db/minesweeper/reward_economy/encounter 只读引用+宿主 fatal Callable——防 _init max-parameters 新键) | A1 board_generator(件内持有);A2 install_board;B4 roll_board/board_skew 写;A4 变异决议行集经 ConfigDB;board_gen/variant_place 两封闭流随件、流名与开流时点零漂移 | 无(零 await——装板全同步) |
| **RoundCycle**(`core/round_cycle.gd`,B1 域·轮循环驱动件,已建件——迭代 44) | 轮循环驱动域唯一承载(自宿主收编 7 成员):结算五段段件持有与驱动、重入门闸、开轮摇号、踩雷账 | 三参直构(host/fx_host_ref/reward_grant_ref——五段构造透传不落成员);段驱动入口由宿主接线调用;实测类变量 8/公开方法 3 | B1 宿主;settlement/ 五段段类;A3 相位读 | 无 |
| **BattleFacts**(`core/battle_facts.gd`,B1 域·战斗事实件,已建件——迭代 44) | 战斗事实域唯一承载(自宿主收编 14 成员):受理事实(encounter)、轮 facts 缓冲(reveals/seals/path)、封印判定账、事实溯源、同调快照读面;attune 状态两成员随件(行为函数留宿主) | 零参直构;宿主与段类经注入引用读写;实测类变量 15(贴顶)/公开方法 4(reset_round+三 mark 写口) | B1 宿主;A2/A3 只读 | 无 |
| **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 | 无 |
| **run_structure**(C2) | 冒险/关卡序列、失败重开、**master_seed 唯一接线方** | (契约规范源:`../../gdd/systems/冒险循环/冒险结构.md`) | RngService.set_master_seed、C3/D2 | 无 |
| **difficulty**(C3) | DifficultyTable 回填、全部曲线数值调参所有权、战斗期望表(切片 monte_carlo 为工具原型) | 调参输出(经 D2 R1 解析/编排器传递,禁反向调用生成) | ConfigDB、B1 对账输出(调参约束清单)、A1 报告 C | 无 |
| **meta_progression**(C4,Alpha) | MetaTable、钥石账本、天赋树域(MetaTreeTable 12 节点 v0.8——Fork 一节点双态/row 字段退役/RULE_KEYS 六键/彩蛋特化觉醒,迭代 21 T1 换代)+ 同调位槽位判定(树点亮数单源——T4 槽位收编)。**2026-10-27 全清设计定稿:存档内元进度数据读写原语归 MetaStateStore(新件),本类纯转发读方法随件退役(消费方直连 store),业务规则与写口编排留本类(≤20)** | tree_can_buy/tree_buy/tree_buy_fork/tree_toggle_fork(写口唯一)、hero_override 注入(树淬炼 flat+锻造路线加值位预留 0)、tree_rule_keys | D3、C2、ConfigDB | 无 |
| **meta_progression**(C4,Alpha) | MetaTable、钥石账本、天赋树域(MetaTreeTable 12 节点 v0.8——Fork 一节点双态/row 字段退役/RULE_KEYS 六键/彩蛋特化觉醒,迭代 21 T1 换代)+ 同调位槽位判定(树点亮数单源——T4 槽位收编)。**存档内元进度数据读写原语归 MetaStateStore——已落地(迭代 41):本类纯转发读已退役(消费方直连 meta_state),业务规则与写口编排留本类;hero_override 内联锻造加值与树淬炼 flat;并组三组(forge_select(kind,route_id)/hero_type_axes()→{attack,armor}/tree_camp_status()→{rows,fork_groups});补 meta_state 成员装配** | 公开面 20 定稿:buy_treasure_unlock / refresh_treasure_unlocks / treasure_shop_status / forge_buy_level / forge_buy_unlock / forge_select / hero_type_axes / forge_shop_status / settle_adventure / hero_override / tree_can_buy / tree_buy / tree_toggle_fork / tree_buy_fork / tree_rule_keys / tree_camp_status / toggle_attunement / attune_camp_status / attune_refresh_charges / buy_attune_refresh | D3、C2、ConfigDB | 无 |
### 战斗域装配现状(迭代 27——2026-10-17 用户裁写入;RunLedger 段已随迭代 39 拆件批退役,四账去向见下「冒险域拆分(现状登记)」节)
@@ -132,14 +136,16 @@
### 战斗编排域拆件定稿(2026-10-26 用户裁写入——gdstyle 存量债波 1 设计闭合;同日独立审查(grilling+godot-master 双子代理)阻断 8 条去重 6 组全折入本节,v1.18;2026-10-28 迭代 40 实现批合回(合并提交 514457d1),本节为现状登记)
- **轴甲两件**:battle_orchestrator(1313 行/50 成员,S13 全库单文件最大户)按变化原因拆出 BattleFxHost(效果引擎宿主域)与 RewardGrant(奖励授予域)两协作件;宿主保留架构在册职责全部——战斗状态机与生命周期、轮 facts 缓冲(_facts_reveals/seals/path)、A2 三事件订阅、结算五段驱动、attune 族原位、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(presentation/diagnostic_snapshot 唯一出口不变)、刷新面、nest_preview_count(巢穴预告读数口——外部直读面 main.gd 与 treasure_effects_test 随批点名冻结)。拆件动机与残留债口径见待办清单「gdstyle 存量债全量清偿」条目。
- **轴丙一件(2026-10-29 尾波清偿批设计闭合——实现批建件)**:battle_orchestrator 装板域六函数(`_install_new_board`/`_resolve_global_variant`/`_place_variants`/`_place_variants_apply`/`_variants_fringe`/`_adjacent_to_packs`,约 140 行;全仓仅宿主三调用点——begin_battle/中途刷新受理/player_request_refresh,实查零外部调用)按变化原因拆出 BoardInstaller,职责语句「棋盘安装与变异布置域」(生成→全局变异决议→装板受理→资源摇号→变异布置;单一变化原因=棋盘设置顺序与变异布置算法)。形态两修正(实测裁,与初案「逐字搬运」之差):①宿主复位块不随件——`_install_new_board` 体内五处计数清零、板序推进、board_changed 发射与秘宝叠加层退役留宿主薄壳 `_install_and_reset`,三调用点改调薄壳、时序逐字;②`_board_generator` 全文件仅装板族使用——实例随件自建自接线(fatal 经注入 Callable),宿主成员退役(类变量 38→37,禁无效成员义务)。encounter(23 处跨宿主)与 _reward_economy(18 处)确认留宿主、经引用束注入。文件长键(1035/1000)随本件迁出归绿;attune 族与 begin_battle 受理段序禁动不变。
- **轴丁两件(2026-10-29 零豁免裁设计闭合——实现批建件)**:RoundCycle(轮循环驱动件)与 BattleFacts(战斗事实件)见功能层表两行——宿主 38(轴丙落地后 37)→15,S13 豁免第三条注销;宿主保留面改登记为「战斗状态机与生命周期、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影(两快照出口签名不变)、刷新面、nest_preview_count」;attune 族处置由下一条改判;treasure_inventory 注入改址追认——原「外部赋值路径零改址」句失效,level_runner 经 RewardGrant 出口直注;成员级对账以需求卡片为卡内契约(开工 T0 复核)。
- **跨件单向接线**:RewardGrant 持 BattleFxHost 引用(treasure_used 事件发射与计数器消耗);BattleFxHost 对授予面走 bind 时注入的兑付 Callable(揭示队列 flush 回调)——禁双向强引用(两件均 RefCounted,环引用随每战新建泄漏)。行发射经 bind 时注入的 emit Callable(协议面单点留宿主——CurioDeck bind_handlers 同款先例,禁件回调宿主私有族);轮 facts 缓冲留宿主、fx 件经注入取值;_seal_consumed 留宿主(与 A2 seal_judged 订阅同域);fatal 经注入 Callable 转宿主 _fatal(battle_result 状态机唯一写点不变)。注入 Callable 亦属接线面、同单向纪律——禁为省参数改持对象引用。授予件对账本零强引用:战斗期消费经 `battle_hooks` 三键回调束(attune_consume/vision_add/negative_blast,bind_battle 缺键 assert),宿主侧三单点记账(2026-10-28 迭代 40 合并适配实装——RunLedger 类已随迭代 39 退役,直持对象引用无处可指)。
- **战斗域读面(RunLedger 退役后)**:账本读经编排器 `_journal()`/`_attune_loadout()` 现值派生(GainJournal/AttuneLoadout,enc 派生、无成员接收);冒险桶经 `enc["adv_bucket"]` 下传 `battle_fx_host.mount_buckets`;战末 bonus 四轴经 `GainJournal.gain_bonus` 汇入(2026-10-27 迭代 39 主角账三件落地、迭代 40 合并适配承接)。
- **settlement/ 段类随动**:段类构造注入直持宿主+两新件三引用(Middle Man 禁令封转发壳——调用点改指),授予段读写面点名:_grant_routed×5/_submit×6/escrow 写/_fx_new_resource_types/_feat_first_dig_used/_fx_bonus_delta 写/_apply_ac_bonus/_fx_enqueue_reveal/_flush_pending_reveals/_grant_clear_reward/counters_ctx;战斗段 _defer_board_damage 复位与 _deferred_board 结账改指 RewardGrant。四个授予段 helper(_feat_yield_adjust/_harmful_variants/_harmful_adjacent/_hits_of)与 _pool_effective/perfect_sweep_earned 留宿主(段类经宿主引用直调,现状形态)。
- **attune 族原位保留**:同调配装 8 成员不进新件——迭代 39 副卡(AttuneLoadout 充能单源化)语义领地,两批零冲突;本波残留债归尾波。RewardGrant 经宿主 attune 读投影取 _attune_safe_seen。
- **attune 族处置(2026-10-29 零豁免裁改判,原「原位保留」失效)**:attune 状态两成员归 BattleFacts、行为函数留宿主——迭代 39 副卡(AttuneLoadout 充能单源化)语义领地不破,外部三面消费点零改址;RewardGrant 经宿主 attune 读投影取 _attune_safe_seen 形态不变。
- **呈现零变化**:两快照出口签名与载荷逐字不变,内部改读两件只读投影(快照本体不读揭示态——揭示态投影消费方=宿主 A2 订阅旁路判定);treasure_interact_controller 三触点分家——use_treasure 经组合根现值回调改注 RewardGrant(每战新建编排器,禁一次性直引防悬挂),minesweeper 读与 attune_convert_state 仍走宿主取值回调(双取值面);nest_preview_count 直读面零变化。
- **S13 键账收敛义务**:12 个超标键随函数迁入新文件=基线外 NEW(use_treasure 长度/分支/局部/返回×4、_grant_routed 参数 8、_dispatch_row 参数 6、_apply_percent_row 参数 6、_apply_gp_row 参数 6、_apply_flat_row 参数 7+返回 9、_submit_flat_hero 参数 7、_fx_fact 返回 7)——卡内同步收敛为本波义务(_grant_routed 等签名随引用束注入改形、use_treasure 体拆分),禁波 0 合并 refresh 吸收新文件未还键;新件 _init 引用束注入防 max-parameters 新键。
- **验收红线**:游戏行为零变化(RNG 消费序零漂移——fx_reveal 封闭流随件迁移取号序不变、信号顺序契约冻结、明细行协议词表零触碰、同种子逐位一致);battle_orchestrator 头注发射点指址随拆件改写(词表不动,指址须真);行数恶化债清偿义务(迭代 38 特批带红合并挂账 c4c69701——本波还债归绿);PARITY 触面(battle_orchestrator.gd 被改)实跑对拍须经用户批准、不得借机重锚;S13 改善非清零口径(宿主残留 8–10 键+随迁键卡内收敛后新件零残留为达标线)。
- **S7/S8 随动**:RewardGrant 调 B4 API,两新件一并登记进 S7 B1 域文件集与 S8 结算路径 await 文件集(static_scan.sh 两清单独立、同批更新——防 await 面漏闸)。
- **验收红线**:游戏行为零变化(RNG 消费序零漂移——fx_reveal 封闭流随件迁移取号序不变、信号顺序契约冻结、明细行协议词表零触碰、同种子逐位一致);battle_orchestrator 头注发射点指址随拆件改写(词表不动,指址须真);行数恶化债清偿义务(迭代 38 特批带红合并挂账 c4c69701——本波还债归绿);S13 改善非清零口径(宿主残留 8–10 键+随迁键卡内收敛后新件零残留为达标线)。
- **S7/S8 随动**:RewardGrant 调 B4 API,两新件一并登记进 S7 B1 域文件集与 S8 结算路径 await 文件集(static_scan.sh 两清单独立、同批更新——防 await 面漏闸);轴丙 BoardInstaller 同规(调 B4 roll_board,实现批随批增行)。
### 冒险域拆分(现状登记——2026-10-26 设计闭合,2026-10-27 迭代 39 拆件批实装,本节各条即现行形态)
@@ -147,7 +153,7 @@
- **CurioDeck 转纯池**:bind_handlers 反向回调退役;商店购买消费经注入 Callable 链至 CurioStop 兑付单点——牌库只管抽签/消耗/cap 判定/凭证计数。
- **RunLedger 退役**:四账各归其主——加算区/购买记录/钥石增量/特性集/真视记录归 HeroLedger,充能归 AttuneLoadout,冒险桶字典归应用层持有;战斗侧 encounter 注入面由单一账类引用改为多路(桶字典+实体账+配装件);ShopSession 账本引用面同步换代,商店属性商品加算改经 HeroLedger 单写原语。
- **level_runner**:依赖改构造注入一次给齐(后置 setter 群与商店重绑机制退役;注入活引用禁快照——特性集共享引用语义保留);商店白盒转发属性撤除;战斗→关卡信号转发族保留(战斗实例每节点新建,关卡层做战斗域稳定面)。
- **断点片段化**:各件自写自读自己的键(关卡层交关卡域五键片段:node_id/visited_ids/aux_items/shop_shelf/shop_visit_buys),缺键回退策略随领域知识归各件;应用层唯一落盘口(收齐各件片段拼成顶层快照后写盘),写触发点不变(五处);既有 27 键逐键冻结;新增获得日记键沿扩列不升版先例——旧档缺日记回退汇总键直载并跳过比对,日记在档时由日记推导汇总、与留存值不符即报错中止(迁移正确性自检)。
- **断点片段化**:各件自写自读自己的键(关卡层交关卡域五键片段:node_id/visited_ids/aux_items/shop_shelf/shop_visit_buys),缺键回退策略随领域知识归各件;应用层唯一落盘口(收齐各件片段拼成顶层快照后写盘),写触发点不变(五处);既有 27 键逐键冻结;新增获得日记键沿扩列不升版先例——旧档缺日记回退汇总键直载并跳过比对,日记在档时由日记推导汇总、与留存值不符即报错中止(迁移正确性自检)。2026-10-29 零豁免裁随动:拼盘与读侧策略机组拆出 breakpoint_store(核心层表新行),应用层落盘口与写触发点不变——宿主经件组装快照后写盘,27 键冻结不变。
- **信号直连**:adventure_runner 六转发信号族(node_entered/choices_presented/step_row/round_started/board_changed/wallet_changed)退役;呈现层在 level_started 重取点重订阅关卡对象信号(旧实例连接随引用释放消亡);「关卡开始信号前零发射」契约写进关卡件头注;「恰好连接一次」断言用 is_connected 守卫(release 模板剥 assert);三选一信号面归 CurioStop。
- **调用直连**:不留中间人委托壳(纯转传=Middle Man 反模式,2026-10-26 用户裁定);呈现控制器经取值回调持件——回调只许捕获长活根对象,禁件方法 Callable(防悬挂);约 30 个调用文件改址;测试/探针直连件读写。
- **验收红线**:游戏行为零变化(RNG 流名 reward_pick 与消费序零漂移、信号顺序契约冻结、同种子逐位一致);gdstyle 基线禁改(类变量计数下降属改善);怪物寻址 resolve_enemy_rowset 调用面与关卡参数档装配零变化(未来关卡主题的数据缝不堵)。
@@ -158,11 +164,15 @@
|---|---|---|---|---|
| **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 图片参数两轮变化) |
| **组合根拆件**(呈现侧写口子件:curio_interact_controller / camp_shop_presenter / treasure_interact_controller) | 代组合根执行核心写调用(冒险节点后续推进/营地购买与刷新/道具使用——依赖与回调由 main 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 |
| **ui_registry**(`presentation/ui_registry.gd`,界面注册面,已建件——迭代 44) | 呈现层接口面唯一承载:层序合同(LAYER_CONTRACT)迁驻与合同寻址、三域句柄容器 ui_screens(9 屏壳与覆盖层句柄)/ui_battle(5 战区句柄)/ui_modals(13 模态句柄+ModalFlow);组合根经其一级寻址接线,探针挂载假设迁移为 `main.ui.<域容器>.<句柄>`;七个探针别名/转发成员随批消灭 | 三容器属性+合同寻址口(RefCounted 门面,不挂树、零会话期引用);四件(UiRegistry/UiScreens/UiBattle/UiModals)类变量 3/9/5/14,ModalFlow 随 UiModals 容器构造即建 | 构造注入:三屏壳、覆盖层、战区句柄、ModalFlow(main_ui_build 交付随之改型) | 无(RefCounted) |
| **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 静态) |
| **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_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 |
| **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) |
@@ -180,7 +190,7 @@
### 三屏壳与组合根句柄面
- 呈现场景拆为三屏壳:MenuScreen(开始菜单屏)/CampScreen(营地屏)/AdventureScreen(冒险屏——内含关卡地图与战斗两容器)。
- 屏成员经组合根 main.gd 一级句柄暴露(探针挂载假设 `main.X` 路径零迁移;**S13 豁免登记 2026-10-27:组合根类变量面即探针契约面——约 46 个句柄成员被探针挂载假设钉死,max-class-variables 对本件按架构契约豁免(键永久基线内、禁再恶化),豁免理由在册本节,禁为清键改写探针挂载契约**);局态一律住 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——冒险节点后续推进、营地购买与刷新、道具使用)。视图禁直连拆件;核心写调用的可达路径仍是「视图→信号→组合根(含拆件)→核心」,依赖规则与组合根单点装配不变。成员见「模块归属·呈现层」表。
### 视觉层装配合同
@@ -228,14 +238,14 @@
| **run_system_test**(系统测试运行器) | `tools/run_system_test.py` | 系统测试总入口:headless 跑真实玩法路径断言面(宿主=`res://tests/system_test.gd`)+ 三闸判定(口径同 run_smoke,摘要行兼容两种宿主格式) | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备(同 run_smoke:退出码/摘要失败数/脚本错误扫描;wall-clock 超时在位) |
| **run_all**(单元与集成测试) | `game/tests/run_all.gd` | 64 套统一入口(61 单元+3 集成,2026-10-28 实数):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸);选跑态 `--only=<词[,词…]>`(直跑形态对套件路径子串匹配只跑命中套件,零命中=用法错 quit(2),仅供开发期最窄确认——不产闸回执、不作验收凭据,批末全量闸不经选跑分支) | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;三硬闸齐备(入口内扫子套件输出 + 编排层扫 `unit.log`;`Kit.finish` 零断言返回专用码 2)。子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空 |
| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排:默认段 ui_input_probe / ui_struct_probe / layout_probe / ui_contrast_probe / **screen_flow_probe**(屏壳切换),`--click` 加跑 ui_click_probe,`--full` 加跑 ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);判定统一走 `verdict` 函数——退出码为唯一权威判据,PASS 串降级为附加判据,逐段跑 `tools/error_scan.py` |
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);**SCRIPT ERROR 扫描缺失**;断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零;⑤三段探针日志逐段脚本错误扫描 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);SCRIPT ERROR 扫描在位(逐段 `tools/error_scan.py`,标记与 run_smoke 同源);断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
| **static_scan**(静态扫描域) | `game/tests/static_scan.sh` | 静态扫描域 S1–S10 可执行载体(grep 规则 + `tools/audit_type_hints.py`〔S9〕+ `tools/audit_file_headers.py`〔S10〕) | fail 聚合退出码(任一硬失败规则命中即 exit 1) | 查退出码在位;SCRIPT ERROR 扫描与断言数下限不适用(逐规则命中即红,无运行时错误面与断言计数);S3 现为 warning 级、不参与 fail 判定(见下表) |
### 改动域划分(2026-10-17 用户裁「代码批只跑代码闸、文档批只跑文档闸」)
| 域 | 闸 | 本批改动落在这些路径即属该域 |
|---|---|---|
| **代码域** | run_smoke、run_system_test、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan、回放确定性对拍(条件件) | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 |
| **代码域** | run_smoke、run_system_test、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 |
| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` |
| **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — |
@@ -257,7 +267,7 @@
| 硬闸 | run_smoke | run_system_test | run_all | probe_gate | render_gate | static_scan |
|---|---|---|---|---|---|---|
| **退出码优先** | 已落实(闸①) | 已落实(闸①,口径同 run_smoke) | 已落实(套件 manifest expect 协议) | 已落实(`verdict` 函数逐段判退出码,PASS 串不作判据) | 已落实(段级退出码 + 取证双检) | 已落实(fail 聚合退出) |
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(闸③,标记清单与 run_smoke 同源) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | **缺失**(本批未收——判定主体是 golden 容差与取证存在性;登记待办清单) | 不适用(静态域无运行时错误面) |
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(闸③,标记清单与 run_smoke 同源) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | 已落实(三段探针日志逐段 `tools/error_scan.py`,标记与 run_smoke 同源) | 不适用(静态域无运行时错误面) |
| **断言数下限** | 无独立下限(闸②摘要行必须在案,防「跑一半仍 PASS」;脚本错误吞断言路径由闸③兜住) | 同 run_smoke(过渡期宿主摘要行必须在案) | 已落实(`Kit.finish` 零断言返回专用退出码 2——与「断言失败=1」区分,防致命类套件的 expect 协议放过) | 已落实(退出码 0 但无该探针签名标记亦判红——防静默退出) | 不适用(golden 容差 + 取证存在性判定) | 不适用(逐规则命中判定;另有扫描面为空即判红的零命中自检) |
背景事实(run_smoke 头注在案):GDScript 运行时脚本错误不改进程退出码、不计入冒烟失败数、且静默中断所在函数剩余断言——2026-09-19 状态记录 83 实证「309 项 0 失败」实为断言未跑全。这是 SCRIPT ERROR 扫描必须存在于判定层的原因。
@@ -308,9 +318,11 @@
本节为测试域(测试载体、判定口径、静态扫描域)的事实源。新增违规案例仍须先补 static_scan 规则再修码(code-conventions 缺陷-规范对照表纪律)。
**S13 测量面豁免登记(2026-10-27)**:`game/presentation/tune_sim_controller.gd` 的循环内 `await`(测速循环逐帧让出)是调参测量的 wall-clock 步进承载语义——重构即测量口径漂移,违行为零变化红线。该键按架构豁免(键永久基线内),豁免理由在册本段;同件 `print` 残留不豁免(信息已由 toast/JSONL 承载,随全清批删除)。
**S13 测量面豁免注销(2026-10-29 零豁免裁)**:原循环内 `await` 豁免〔2026-10-27〕注销——tune_sim_controller 改 process_frame 信号常驻连接+步进状态机(每帧一局、帧相位逐点对齐,已建件——迭代 44 卡③);运行态收尾现行形态=run() 尾每批一次执行(卡③ v1.0 申报的「每局置假还原」前提已被迭代 41 测试债批 215da315 先行消化,行为改动申报随 v1.1 停卡重裁剥离)。
**S13 豁免登记第三条(2026-10-28 迭代 40 卡①)**:`game/core/battle_orchestrator.gd` 的 max-class-variables 键——宿主保留面为「战斗编排域拆件定稿」节第 1 款锁定的职责承载面(状态机与生命周期、账本读面、attune 族原位、行发射基建、呈现投影),类变量阈值 15 在保留面锁定下不可达,按全清口径第二支臂登记架构豁免(键永久基线内、禁再恶化;实测键账 50→38)。
**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)。基线登记值属欠估;闸机扫描与基线同源、判定口径自洽不误报,债的真值以实查为准——零豁免各批按真实口径设计(实数低于阈值,不依赖解析缺陷)。**清偿结果(2026-10-31)**:board_cell_unit 30→10、standee_body 19→10 已随迭代 45 真实清偿,基线 refresh 已收账(欠估口径旁证=33/31 声明与实测吻合)。登记待办:向 gdstyle 上游报 issue(仍在册)。
---
@@ -346,7 +358,7 @@ D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB
### 3. 存档/读档路径
- 存档系统已实装:格式、三槽位、断点域与迁移链的现行契约以 `design/gdd/systems/表现与支撑/存档序列化.md`(owner)为准。
- RNG 快照契约(现行仍成立):断点续玩回填 = master_seed + 各流 scope 计数器;快照切出须发生在无正在执行中的 open_stream 实例时(scope 事件边界外——B1 编排保证:切点 = board install 边界);SCHEMA_VERSION 高版本拒读。
- RNG 快照契约(现行仍成立):断点续玩回填 = master_seed + 各流 scope 计数器;快照切出须发生在无正在执行中的 open_stream 实例时(scope 事件边界外——B1 编排保证:切点 = board install 边界);SAVE_FORMAT_VERSION 高版本拒读。
### 4. 打包与字体(CJK 随包发行)
+1 -1
View File
@@ -54,7 +54,7 @@
## 3. 测试纪律
已独立成文:`design/engineering/test-conventions.md`(探针标配 / 回归断言 / 提交前全量验证 / 验收清单落码规范)。
已独立成文:`design/engineering/test-conventions.md`(探针标配 / 回归断言 / 提交前验证 / 验收清单落码规范)。
---
+9 -7
View File
@@ -18,7 +18,7 @@
写文档像向朋友当面解释你的游戏:
1. **完整句子优先**:一段只说一件事,长句拆短句。
2. **禁止碎片笔记体**:不写压缩碎片,不把 `A→B→C` 箭头链当叙述(流程交给字符画,见第 3 节),不用只有作者自己懂的缩略语。
2. **禁止碎片笔记体**:不写压缩碎片,不把 `A→B→C` 箭头链当叙述(流程交给图,见第 3 节),不用只有作者自己懂的缩略语。
3. **每节人话开头**:每小节第一段先用白话说清"这是什么、为什么需要它";只读各节第一段就能看懂全文故事线。
4. **术语首现即解释**:专业术语或项目概念首次出现用一句话解释;玩家可见专属名词(如"显影尘")同样要说清。
5. **叙述优先于引用**:AC-x、F-x 等编号引用不进正文叙述,只出现在验收标准与附录。
@@ -30,11 +30,11 @@
|---|---|---|
| 规则的来龙去脉、行为逻辑 | 散文段 | 每段 ≤240 字,一段一件事 |
| 枚举、数值、边界、依赖 | 表格 | 表格只放可列举的事实;禁把需要解释的内容塞进单元格让读者自己拼 |
| 流程、状态流转 | 字符画(代码块) | 禁用散文逐步描述流程 |
| 流程、状态流转、结构关系 | 文档内嵌 HTML/SVG(默认)· Mermaid(网页发布保底)· 字符画(无渲染环境保底) | 禁用散文逐步描述流程;默认在文档内嵌 HTML/SVG(表现力优先,本地预览即渲染,图体行数计入节点篇幅);发布到会过滤内嵌 HTML 的网页端时改 Mermaid;无渲染查看环境用字符画 |
| 公式 | 结论先行 | 先一行加粗结论,再放公式代码块、变量表与验算表 |
| 体验锚点 | 关键词表 | 支柱/关键词/机制承诺 三列表,禁长篇情绪描述 |
节点内顺序:先人话,再表格枚举,再字符流程图,最后公式举证。**规则两段式**:先写"人话阶段"(作用于哪个系统、什么情况下、谁触发、触发后发生什么),再写"举证阶段"(逐效果给出计算方法;纯流程规则明写"无计算")。规则名禁止纯名词短语(反面例:"获取路由")。
节点内顺序:先人话,再表格枚举,再流程图(内嵌 HTML/SVG 或字符画),最后公式举证。**规则两段式**:先写"人话阶段"(作用于哪个系统、什么情况下、谁触发、触发后发生什么),再写"举证阶段"(逐效果给出计算方法;纯流程规则明写"无计算")。规则名禁止纯名词短语(反面例:"获取路由")。
## 4. 用语纪律(见名知意 · 所见即所得 · 禁自造黑话)
@@ -67,8 +67,10 @@
| 一页纸节点 | 一节、一条规则 ≤一页纸(约 ≤80 行);装不下就拆节 |
| 一系统一文档 | 不建 analysis 推演件;两三句能说清的"为什么"直接写进设计目的或规则正文 |
| 正文只写现行规则 | 禁删除线、"(作废)"、"(日期+拍板)"式注记;审计信息入对应文档附录 |
| 禁实现内容 | 系统文档与概念文档全文禁写实现细节——代码路径(`.gd`/`.py`/`.sh` 与运行时文件路径)、测试文件名、断言名、类名/方法名/信号名/常量名/调试与遥测入口名、提交号一律不出现;唯一例外=数据索引(`game/data/tables/*.tres` 这类表路径与表名、表字段名与表内枚值、存档键、封闭词表标签)。实现事实归 `architecture.md`;验收标准只写玩家或系统可观测面。本行由文档域闸的能力审计机检两个可判死的面(带后缀路径、反引号调用形态),其余靠写卡段人工核对;闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部 |
| 正文单点维护 | 一条规则句只写在定义它的那份文档(owner 文档);其他文档要引用时写指针或语义概括,禁复制色值、尺寸、路径等可核对的具体值——上一句管不住数值之外的双写,本句补上。豁免面 = 执行侧文件必须带字面值才能工作时(生图提示词里的十六进制色、脚本参数),转写不算双写,但该文件头部必须写明"冲突时以 owner 文档为准" |
| 禁写变更记录 | 文档与代码文件内一律禁设「版本历史/修订记录/变更记录/修改记录/更新记录/更新日志」节或表格,禁逐版本叙述「改了哪些条款」;沿革与改动明细只进 git commit log。版本号可作为文档现行身份标记出现在文首状态行与正文引用中,但不得再挂变更表 || 后置 ≠ 删除 | P1/P2 后置内容逐条列出;复活后置项 = 修订文档,禁静默加回 |
| 禁写变更记录 | 文档与代码文件内一律禁设「版本历史/修订记录/变更记录/修改记录/更新记录/更新日志」节或表格,禁逐版本叙述「改了哪些条款」;沿革与改动明细只进 git commit log。版本号可作为文档现行身份标记出现在文首状态行与正文引用中,但不得再挂变更表 |
| 后置 ≠ 删除 | P1/P2 后置内容逐条列出;复活后置项 = 修订文档,禁静默加回 |
| 活体清单不冻结计数 | 对会生长的清单(文档树构成、UI 层项、RNG 流词表等)不写死计数与枚举——写指针到 owner(根索引/常量表/实现文件),现状值由 owner 承载;历史计数只进 git 提交信息 |
**数值纪律**:正文定规则、取值域、单位;平衡值进 `data/tables/`,provisional 必须标注;已锁数值事实源 = `game/data/tables/*.tres`(现行值)与 owner 文档正文(规则、取值域、单位)。
@@ -96,7 +98,7 @@
| `待办清单.md`(仓库根) | 流水线唯一登记处:条目=登记元数据标题+四属性正文,状态随流水线段推进(格式与状态机见第 12.1 节);各系统文档「待办」节只留指路存根 |
| `roadmap.md`(根目录) | 里程碑表+迭代总表,一行一迭代,记录编排与实现进度(六列格式见第 12.2 节) |
**系统文档骨架**(`systems/` 下统一采用):文首引言(一句话定位+状态)/设计目的(2-4 句白话)/系统范围(负责/不负责两表)/在核心循环中的位置(字符画一行+两句白话)/核心规则(领域章节 ×2–6,每节一页纸)/依赖与输出(表格;引用纪律见 concept-conventions)/P0 最小闭环与 P1·P2 后置/调参表(取值域+provisional 标注)/验收标准(可验证判据)/待办(指路存根)。
**系统文档骨架**(`systems/` 下统一采用):文首引言(一句话定位+状态)/设计目的(2-4 句白话)/系统范围(负责/不负责两表)/在核心循环中的位置(字符画一行+两句白话)/核心规则(领域章节 ×2–6,每节一页纸)/依赖与输出(表格;引用纪律见 concept-conventions)/P0 最小闭环与 P1·P2 后置/调参表(取值域+provisional 标注)/验收标准(玩家或系统可观测的判据,禁实现细节)/待办(指路存根)。
**命名**:中文、见名知意;禁 a1_/b1_ 编号前缀;不留纯空目录。正文指代子系统一律用中文规范名;英文代号对照表见 `02-系统清单.md`,旧代号(a1–d6)仅限解读归档。
@@ -134,7 +136,7 @@
### 12.1 待办清单条目格式与状态机
待办清单是流水线的唯一登记处:要做的事先登记一条,随流水线段推进改状态,做完整条删除。一条条目分两层——**标题=登记元数据**(何时登记、从哪来、紧急度,登记后不再变),**正文=四个属性**(随进程演进)。四个属性各管一件事:`实况` 管凭据,`缺口` 管这条的**输入**(还缺哪一次设计),`改动面` 管这条的**产出**(要动哪些面),`状态` 管这条走到流水线哪一段。
待办清单是流水线的唯一登记处:**现在不做、以后才做的事**登记一条,随流水线段推进改状态,做完整条删除。条目记录某件事走到了哪一段,不是各段的开工前置。一条条目分两层——**标题=登记元数据**(何时登记、从哪来、紧急度,登记后不再变),**正文=四个属性**(随进程演进)。四个属性各管一件事:`实况` 管凭据,`缺口` 管这条的**输入**(还缺哪一次设计),`改动面` 管这条的**产出**(要动哪些面),`状态` 管这条走到流水线哪一段。
标题格式(尖括号处按实际填写,方括号内为可选段):
@@ -171,7 +173,7 @@
文档线:待文档设计 → 文档设计中 → 文档设计已闭合
美术线:待美术设计 → 美术设计中 → 美术设计已闭合
下游共用序列(需求改动):待写卡 → 已写卡 → 已编迭代 N → 完成即整条删除
直改序列(工程改动/文档设计改动/美术素材改动):登记 → 待执行 → 完成即整条删除(经设计讨论的可前置闭合段,如「文档设计已闭合+待执行」)
直改序列(工程改动/文档设计改动/美术素材改动):待执行 → 完成即整条删除(经设计讨论的可前置闭合段,如「文档设计已闭合+待执行」)
累积示例:文档设计已闭合+待美术设计+已写卡
```
+11 -11
View File
@@ -7,22 +7,22 @@
## 1. 分支纪律(车道铁律)
改动按文件性质分两车道(2026-10-26 用户裁,取代 2026-09-19 立的「一切改动走 worktree」铁律;判据与 §3 第 8 条改动域划分同一):
一切面主工作区直改+直提(2026-10-30 用户裁本地 CI 化:验证挪到合回后,主区直提不再拦任何面;判据与 §3 第 8 条改动域划分同一):
1. **worktree 批车道(唯一强制面=代码面)**:一切触及代码面(`game/` 下非 `.md` 的行为载体:代码、测试脚本、探针、场景、数据表、素材)的改动——迭代实现批与触及代码面的工程改动(如纯测试维护)——先建独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;所有改动与验证都在 worktree 内进行(未跟踪文件不会出现在新 worktree,需手工搬运);建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。迭代批开工细则见 §3 与 iter 技能
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),用户点名或当场问才跑
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,非规则强制
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
1. **主区直改车道(一切面)**:`game/`(含代码面=`game/` 下非 `.md` 的行为载体)、`design/`、`reports/`、`tools/`、根规则与清单文件、`.zcode/`、`.qoder/`——直接改、直接提交。验收分两层:commit 点 git pre-commit 钩子(`tools/hooks/pre-commit`,经 `core.hooksPath` 挂载,在位性由 run_all_gates.sh 跑批自检)现场跑 `tools/run_all_gates.sh --docs`(秒级文本扫描)绿即落地红即拦,合并收尾提交跳过(MERGE_HEAD 在,内容归合回后 CI);代码面的全量验证归合回后本地 CI(下条)。开发期纪律不变:卡内只测自己改的东西、出红只跑红件最小单元(§6)
2. **合回后验证(本地 CI,2026-10-30 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh merge` 对 master 尖跑一次全量域闸——唯一强制全量验证点;CI 红态下每个修复提交经 post-commit 钩子(`tools/hooks/post-commit`)自动追绿复验,绿态与无状态零触发。重跑判据:同一份字节禁止第二次人手跑;有意义的重跑只剩「验证修复」一类。CI 红不回滚合并——红项记 `game/tests/.gates/ci-status.json`,修复提交追绿;开工先读 CI 状态,红未修不开新批。跑批起手 `--editor --quit` 刷类名缓存(在册坑:.godot 缓存不入 git,合并后失配假红假绿)——原「合并后零复跑」的秒级动作由跑批件接管。并跑语义=跑完补跑(coalesce):跑批期间新触发不中断当前轮,轮尾对最新尖端补跑,绝不逐笔各跑一轮
3. **worktree 批车道(大批约定,非强制)**:迭代实现批与大体量改动按约定仍走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。收口顺序固定为**冻结 → 件核 → 并 master → 重塑 → 主区合回 → CI 验证**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先 `git merge master` 解冲突;**重塑**=迭代分支(iter<N> 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代工程小批免重塑;然后主工作区 `git merge --no-ff <任务分支>`(合回守卫对迭代分支数卡校验,未归卡提交拦下),合并落地 post-merge 钩子触发 CI,红则修复提交(主区直提)追绿,全绿后 `git worktree remove`+删分支。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑
4. **执法分层(全机械,零自觉依赖)**:①git pre-commit 钩子——一切提交的文档域闸机检(合并收尾提交豁免)。②post-merge/post-commit 钩子——本地 CI 触发(仅主工作区;任务 worktree 内的合并不触发)。③`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:`git commit --no-verify` 拦截(该旗标仅属用户)、iter<N> 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。git 层钩子对一切提交者生效(含用户手工提交,紧急出口 `--no-verify`);ZCode 层只约束 agent
## 2. 五段流水线
**需求改动**(游戏代码,含随逻辑写/改的测试)按五段流水线走:**登记 → 文档设计/美术设计(两条设计线)→ 写需求卡片 → 迭代编排 → 实现**;工程/文档设计/美术素材三类改动不走写卡与编排段,走「登记 →(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸 → 完成删条目」(车道细则见 §1)。两层口径(2026-10-25 用户立):需求卡片=需求单位的实施文档,任何工作量皆可写、皆可编入迭代;工作量限制只属迭代层——迭代估时总量 <5 个工作日=「冻结待凑批」,不可开工。段间不自动流转,每段转进由用户发令;每段一个技能入口(五者均装于项目 `.zcode/skills/`:doc-design/asset-ingest/req-card/iter-plan/iter),登记不立技能(条目格式由流水线闸强制)。设计工作在会话内进行、用户主导、禁无监管自跑。
**需求改动**(游戏代码,含随逻辑写/改的测试)按五段流水线走:**登记 → 文档设计/美术设计(两条设计线)→ 写需求卡片 → 迭代编排 → 实现**;工程/文档设计/美术素材三类改动不走写卡与编排段,走「(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸」(车道细则见 §1)。两层口径(2026-10-25 用户立):需求卡片=需求单位的实施文档,任何工作量皆可写、皆可编入迭代;工作量限制只属迭代层——迭代估时总量 <5 个工作日=「冻结待凑批」,不可开工。段间不自动流转,每段转进由用户发令;每段一个技能入口(五者均装于项目 `.zcode/skills/`:doc-design/asset-ingest/req-card/iter-plan/iter),登记不立技能(条目格式由流水线闸强制)。设计工作在会话内进行、用户主导、禁无监管自跑。
| 段 | 入口技能 | 产出 | 出入口铁律 |
|---|---|---|---|
| 一·登记 | 无(闸管格式) | 待办清单条目 | 条目=标题登记元数据(日期/来源/可选优先级)+正文四属性(实况/缺口/改动面/状态);标题登记后不再变,正文随进程演进。格式与状态机权威=doc-conventions 第 12 节 |
| 二·文档设计/美术设计 | 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 | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务 |
@@ -48,10 +48,10 @@
7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
**完成**
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。所选域全绿才合回 master。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底
9. **合并后主区零复跑**(2026-10-24 用户裁:合回重跑验的是与回执同一份字节,零信息)——合回落地后 code 批只做两件秒级事:`--editor --quit` 重刷类名缓存(在册坑:.godot 缓存不入 git,与合并后代码失配会假红假绿)+冒烟启动确认可开机(合计约 1 分钟);docs 批零动作。全量验证**每批跑一次**=worktree 批末按域闸,顺序是「先并入 master、再跑闸」(§1 第 2 条);只有两种情形允许把这次跑作废重来——① 闸红且修法动了 `game/`,② 闸已开跑后 master 又被推进(合并将产出未测内容,由合回闸祖先判定拦下,§3.9 第六况)。「跑都跑了再跑一遍保险」「闸后顺手改点 `game/` 再补一跑」都不构成复跑理由;master 上永不跑全量
8. **合回后 CI 验证(worktree 批;主区直改小批的验收在 commit 点由 git 钩子执行,见 §1 第 1 条)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动跑全量域闸(不带参数档,02 全套件),判定与红项落 `game/tests/.gates/ci-status.json`。全量是唯一强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时 CI 判红在案(跑不出回执=verdict 红,fail-closed 语义保留)
9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复提交(post-commit 钩子自动追绿复验,每笔修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。类名缓存刷新与开机确认由 CI 跑批起手接管(§1 第 2 条),合并后无手工动作
10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12
11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,要往外交代的东西一律落 `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. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置
13. **中间产物吸收即死**——细则见 AGENTS.md 硬性红线与 doc-conventions 第 11 节
14. **改动级验收闭合**——写卡期列需求侧对照表,实现期补实际对照表(测试文件:行),每个行为改动的验收断言从真实入口进入、在当轮真实跑批中执行并通过(判据绑定面口径=test-conventions §8.4;禁变异类测试与撤销类取证,2026-10-24/28 用户两裁);合回前独立验收审计。细则=test-conventions §8
@@ -62,7 +62,7 @@
**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 采集档位细则
+28 -5
View File
@@ -25,9 +25,9 @@
每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮;
选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。
## 4. 提交前全量验证
## 4. 验证时机(合回后本地 CI 制,2026-10-30 换代)
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`)全绿——该脚本是全部验证闸的唯一入口与唯一清单,本文禁另列闸件清单。闸件增减只改脚本。
开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6);主区小改孤跑验证后直提。全量验证归合回后的本地 CI:git merge 合回 master 触发 post-merge 钩子跑 `tools/run_all_gates.sh` 不带参数档(两域全量,02 全套件——跨卡触面与关联档选测的盲区由全量兜底),结果落 `game/tests/.gates/ci-status.json`;CI 红态下修复提交经 post-commit 钩子自动追绿。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
## 5. 测试函数怎么写——验收清单条目的落码规范
@@ -35,7 +35,7 @@
需求卡片 §8「改动—验收对照表」每一条都要落成可执行的验证;本文定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。
1. **执行载体五选一,逐条声明**:单元测试(`tests/run_all`)/系统测试断言(`tests/system_test.gd`)/场景探针(`probe/` 下 `*_probe`)/MC 同种子逐项比对/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但撤销对应改动时每条都要能单独变红。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但每条都要有自己单独可判红的判据(取证口径见 §8.4 唯一口径——禁变异/禁撤销)。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
3. **断言词汇按验证对象五选一**:
| 词汇 | 适用场景 |
@@ -67,7 +67,7 @@
- 「跑一遍没崩=通过」无效:测试绿只证明**当前代码没犯这个错**,不证明**犯了这个错会被抓住**;断言存在不等于断言执行。
- **脚本错误会静默中断所在函数的剩余断言,而套件仍报全绿**(机制事实):GDScript 无脚本错误陷阱 API,错误不改进程退出码、不计失败数。三处实证:①2026-09-19「309 项 0 失败」实为断言未跑全(状态记录 83);②敌人表行集纪律 10 条断言因定型数组赋值错误从未执行(迭代 29 修复后两条正向见红=夹具本缺锚行);③`Callable` 形参类型写错致解析函数返回 null、后续断言整段蒸发。
- 由此产生两条义务:**写闸件**按 §6 配三硬闸;**写验收函数**必须自证断言全数执行——真实入口断言在当轮执行并通过(§8.4)为主证,零命中自检(扫描面为空即判红)为辅证。
- **覆盖率不是目标**:行覆盖率度量缺失(Godot 无内建,第三方属新基建,是否引入待办清单在册待裁);现行补洞=零指名单测清单+人工判定,判定可靠性靠上列自证手段,不靠覆盖率数字。
- **覆盖率不是目标**:不引入行覆盖率度量(Godot 无内建,第三方属新基建);现行补洞=能力清单覆盖域表+人工判定,判定可靠性靠上列自证手段,不靠覆盖率数字。
## 8. 改动级验收闭合(2026-10-21 立)
@@ -97,7 +97,7 @@
- **反向**:diff 里每个行为改动至少一行;没有对应行的改动,必须写明「非行为改动」或「由第 X 行覆盖」。
- **测试侧**:新增或改写的每条断言至少挂一行;挂不上的孤儿断言删掉或补挂到需求点。
**能力清单维护锚(2026-10-28 用户裁,验证与清单体系整改阶段 1;阶段 2 后定稿)**:逐断言账本 `game/tests/验证集清单.md` 已随阶段 2「存量断言精简」完成退役——冻结对账基准(基点 `b9260138`/1587 条)与精简对账结果(33 站点并为 7、1587→1561,合回 `87d241c1`)留 git 历史。覆盖索引由 `game/tests/README.md`「能力清单 v1」唯一承担:每行一个能力,给真实入口与 2–3 个代表用例(`文件:函数`)。维护义务——**每批收口核对本批受影响能力行**:代表用例可 grep、在本批真实跑批中执行、真实入口未绕过改动点,并刷新「最后验证批次」;新增能力加行,能力退役删行。改动—验收对照表纪律不变:新增或改写的每条断言仍须挂一行(上文「测试侧」);断言改判仍受 §8.4 真实入口口径约束。
**能力清单维护锚(2026-10-29 用户裁改版定稿)**:`game/tests/README.md`「能力清单」是**实现指导+阅读导航+义务声明**——只宣布「要验证什么」,不记「是否已验证/是否跑过」;落地账归现行验收闭合链(改动—验收对照表逐条挂断言+合回前独立验收审计),本清单不承载验证状态。表为四列:能力|系统|真实入口|代表用例(`文件:函数`)。**维护归写卡段,当场入表**:写需求卡片时,本批新增能力当场写入能力行——真实入口取自设计文档已定的入口,代表用例=卡里定死的计划测试(`文件:函数`);受影响存量行(入口/用例名变更)同批更新;能力退役删行。**实现照单落名**:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。**收尾核对**:批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段;实现批禁写该文件的现行红线不变(process-conventions §3 第 11 条),核对义务归收尾提交。三层模型见 §8.9。改动—验收对照表纪律不变:新增或改写的每条断言仍须挂一行(上文「测试侧」);断言改判仍受 §8.4 真实入口口径约束。
### 8.4 真实入口断言(验收闭合的主判据)
@@ -129,3 +129,26 @@
### 8.8 生效与旧卡
本规自 2026-10-21 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改。旧卡在开工前缺对照表的,由写卡段补,补完再开工。
### 8.9 三层模型(验收意图·义务清单·执行证据,2026-10-29 定稿)
验收体系分三层,各层各管一件事,互不代行:
| 层 | 载体 | 切分轴 | 一行是什么 |
|---|---|---|---|
| 验收意图 | 系统文档「验收标准」节 | 按 owner 文档 | 一条玩家或系统可观测的判据 |
| 义务清单 | `game/tests/README.md`「能力清单」 | 按玩家旅程与系统归属 | 一条待验证义务:能力+真实入口+代表用例(`文件:函数`) |
| 执行证据 | 测试套件断言 | 按代码归属 | 一条从真实入口进入的断言 |
四条法则:
1. **每格非空**:每个系统文档有验收标准节;每个能力行有真实入口与至少一个代表用例;每条断言挂在某个需求点或能力行上。
2. **每条断言有归属**:孤儿断言删掉或补挂;不建无归属断言。
3. **数量不守恒、承接守恒**:三层条数天然不等——一条验收意图可由多个能力承接,一条能力义务由多条断言覆盖。要守的是两个满射:每条验收意图至少有一条能力义务承接,每条能力义务至少有一条真实入口断言落地。数量对账只在批内做,见第 4 条。
4. **无全局对账总账**:不建逐断言账本,不做跨层条数对账;对账只在每批的改动—验收对照表里做(§8.3)。历史教训:阶段 2 退役的逐断言账本正因把义务清单层与证据层压成一本账而失修。
**维护时序(2026-10-29 改版:废「滞后一个迭代」)**:能力清单归写卡段维护——写需求卡片时当场入表:本批新增能力当场增行(真实入口取自设计文档已定的入口,代表用例=卡内定死的计划测试 `文件:函数`),受影响存量行(入口/用例名变更)同批更新,能力退役删行;实现批禁写该文件(process-conventions §3 第 11 条)。实现照单落名:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段——清单不记「是否已验证/是否跑过」,落地账在改动—验收对照表(§8.3)与独立验收审计(§8.7)。
**文档纯度**:验收意图与能力描述只写玩家或系统可观测面,禁写代码路径(`.gd`/`.py`/`.sh`)、测试文件名、断言名;唯一例外=配置文件索引(`game/data/tables/*.tres` 这类表路径与表名)。规则正文见 doc-conventions §5「禁实现内容」。
**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上;红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。
+1 -1
View File
@@ -59,4 +59,4 @@
注意:上表六件秘宝为旧目录,已全数退役、只作素材库(新现行事实源=[秘宝](systems/跨局循环/秘宝.md)「目录(重设计 v1)」节——新目录 15 件为代号制、正式命名归用户);新目录正式命名定稿后本表同步。
另:元进度系统资源定名**钥石**——每一层关底 Boss 看守他那块区域的钥石,击杀即取走,用于在存档内解锁永久提升(属性 / 怪物遭遇 / 道具池)。2026-09-14 用户拍板,registry 术语登记随元进度设计批补登。
另:元进度系统资源定名**钥石**——每一层关底 Boss 看守他那块区域的钥石,击杀即取走,用于在存档内解锁永久提升(属性 / 怪物遭遇 / 道具池)。2026-09-14 用户拍板,登记册术语登记随元进度设计批补登。
+1 -1
View File
@@ -15,7 +15,7 @@
| `术语表.md` | 全库专用术语的唯一对照表——危险格/安全格/同调位/RAP/GP/真视等定义+退役词对照(2026-10-11 建立,v1.23) |
| `待办清单.md`(仓库根,不属本树) | 全项目待办事项的唯一登记处;各系统文档的「待办」节只留指路存根 |
| `体验验证清单.md`(仓库根,不属本树) | 需要真人试玩观测的验证项登记处(与待办清单分工:待办清单是"要拍板的事",本篇是"要玩过才知道的事");每条注明结论回流路径 |
| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.19**,2026-10-13) |
| `08-Alpha版本标准.md` | Alpha 版本包含哪些东西的清单——里程碑 M3 的完成标准;只列条目不写内容,定义一律指路各权威文档(**已定稿 v1.20**,2026-10-13) |
## systems/ 五个循环
+8 -8
View File
@@ -2,7 +2,7 @@
> **一句话定位**:本文定义这个项目的 **Alpha 版本包含哪些东西**——它是里程碑 M3 的完成标准,也是判断"还差什么"的对照表。
> **本文只列条目,不写内容**:每样东西是什么、规则怎么走,一律归各自的权威文档,本文只给指路链接。想知道某样东西的定义,请点链接,不要在本文里找解释。
> **状态**:**已定稿(v1.19)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **状态**:**已定稿(v1.20)**——2026-10-13 用户令定稿,历次升版明细见 git 提交史。Alpha 的内容范围自此冻结:增删条目一律须经用户裁决。
> **成文**:2026-10-13。
## 本文怎么用
@@ -110,14 +110,14 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
| 战斗屏 | [battle](ux/battle.md) | 在役 |
| 战斗结算总结框 | [settlement-summary](ux/settlement-summary.md) | 在役 |
| 关卡结算屏 | [level-clear](ux/level-clear.md) | 在役 |
| 三选一(奖励节点 / 关卡完成) | [node-reward](ux/node-reward.md) | 在役(两渠道均三选一停点:关卡完成与奖励节点各停一次,奖励节点经 present_node_curio 拉起兑付) |
| 三选一(奖励节点 / 关卡完成) | [node-reward](ux/node-reward.md) | 在役(两渠道均三选一停点:关卡完成与奖励节点各停一次,奖励节点经兑付拉起开三选一) |
| 商店 | [shop](ux/shop.md) | 在役 |
| 状态面板(局内) | [元进度](systems/跨局循环/元进度.md)(「解锁界面:双入口框架」节) | 在役 |
| 冒险终局面板 | —— | 在役 |
| 图例面板 | —— | 在役 |
| 设置面 | [设置与本地化](systems/表现与支撑/设置与本地化.md) | 在役 |
| 调试浮窗 | —— | 在役 |
| UI 全局主题资源(引擎原生控件接管皮肤) | —— | 在役(迭代 25 · 2026-10-13 入库:game/assets/theme/main_theme.tres 挂 gui/theme/custom) |
| UI 全局主题资源(引擎原生控件接管皮肤) | —— | 在役(迭代 25 · 2026-10-13 入库:主题资源(theme 主资源)挂 gui/theme/custom) |
| UI 按钮状态矩阵(悬停 / 按下 / 禁用 / 键盘焦点补齐) | [待办清单](../../待办清单.md) | 在役(迭代 26 入库:五态矩阵逐实例 override 补齐——悬停 modulate 提亮/禁用 α0.45 幽灵化/焦点 INK 2px 内缩 1px 环/危险钮本族按压态;键盘导航批量面归 Beta「棋盘战斗键盘/手柄输入」条连带点③) |
---
@@ -128,11 +128,11 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
|---|---|---|
| 存档与版本迁移 | [存档序列化](systems/表现与支撑/存档序列化.md) | 在役 |
| 冒险中断续玩断点 | [存档序列化](systems/表现与支撑/存档序列化.md) | 在役 |
| 冒烟套件 | `game/tests/` | 在役 |
| 单元测试套件 | `game/tests/` | 在役 |
| 界面探针 | `tools/probe_gate.sh` | 在役 |
| 渲染闸(截图与黄金图比对) | `tools/render_gate.sh` | 在役 |
| 机器可读的呈现断言(对比度 / 结构) | `game/tests/` | 在役 |
| 冒烟套件 | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 单元测试套件 | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 界面探针 | ——(闸件清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 渲染闸(截图与黄金图比对) | ——(闸件清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 机器可读的呈现断言(对比度 / 结构) | ——(清单唯一出处=process-conventions 运行与验证节) | 在役 |
| 分辨率两档(1280×800 / 1920×1080) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役 |
| 绘制调用数达标 | —— | 在役 |
| 真人试玩轮次 | [体验验证清单](../../体验验证清单.md) | 在役(义务维持;当前轮次未清:㉚/㉛ 未核销,迭代 34 三条目检项待登记) |
@@ -60,7 +60,7 @@
| 不合格产出禁止进玩法 | 产出不过产出规格即无效,无效图绝不上给玩家 |
| 无兜底图 | 不设固定兜底图——软回退一律禁止 |
| 参数无解即中止 | 参数档本身给不出合法图=配置错误,致命中止 |
| 同种子同图 | 同种子+同参数档产出同一张图;随机只经 RngService 统一流 |
| 同种子同图 | 同种子+同参数档产出同一张图;随机只经统一随机流服务 |
### 生成参数:五个旋钮与三套关卡参数档
@@ -104,7 +104,7 @@
## 调参表
数值事实源=game/data/tables(定值批落表);本表只留取值域与设计意图。
数值事实源=data/tables(定值批落表);本表只留取值域与设计意图。
| 参数 | 取值域 | 设计意图 | 状态 |
|---|---|---|---|
@@ -123,7 +123,7 @@ Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选
第二关的"新内容 = 既有池新技能"(技能池构成已定稿入[敌人系统](../战斗循环/敌人系统.md)技能池节)投放时,改的是战斗侧行里引用的技能池内容——节点图结构原样不动,本文无需为投放留任何新结构位。
**节点奖励 = 提升三选一(迭代 10 追加批——用户 2026-10-01 拍板:与通关三选一共享同池)**:奖励节点到达即开三选一(呈现 = 提升三选一覆盖层共用面,spec:[node-reward.md](../../ux/node-reward.md) v2.0)——从提升池七种(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件)按同一令牌库洗牌不放回抽 3(同 kind 去重、同 reward_pick 封闭流),玩家点选一件兑付(选中踢池、未选回池;秘宝件已随迭代 11 第①批出池,满位替换子交互随之不再触发)。件获取次数计数与令牌库为冒险级单例——两渠道同一令牌库合计消耗。原「秘宝投放渠道三选一」(迭代 7 拍板②丙过渡形态)顺带退役;秘宝局外同调件形态已随同调实施批落地(迭代 11 第①批;原提案 §9,git 57da58c 已退役)。「池空直通」为合法态——池耗即枯竭,空库 = 零候选合法态直通(见[冒险结构](冒险结构.md)令牌池节)。
**节点奖励 = 提升三选一(迭代 10 追加批——用户 2026-10-01 拍板:与通关三选一共享同池)**:奖励节点到达即开三选一(呈现 = 提升三选一覆盖层共用面,spec:[node-reward.md](../../ux/node-reward.md) v2.0)——从提升池七种(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件)按同一令牌库洗牌不放回抽 3(同 kind 去重、同一封闭随机流),玩家点选一件兑付(选中踢池、未选回池;秘宝件已随迭代 11 第①批出池,满位替换子交互随之不再触发)。件获取次数计数与令牌库为冒险级单例——两渠道同一令牌库合计消耗。原「秘宝投放渠道三选一」(迭代 7 拍板②丙过渡形态)顺带退役;秘宝局外同调件形态已随同调实施批落地(迭代 11 第①批;原提案 §9 已退役)。「池空直通」为合法态——池耗即枯竭,空库 = 零候选合法态直通(见[冒险结构](冒险结构.md)令牌池节)。
### 关卡收束点:先清库、后宣布
+13 -13
View File
@@ -1,7 +1,7 @@
# 冒险结构
> 一句话:整局冒险的关卡序列、失败与大奖励
> 状态:已定稿(自旧体系迁移);现行为 v1.1
> 状态:已定稿(自旧体系迁移);现行为 v1.3
## 设计目的
@@ -142,17 +142,17 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
| 收支口 | 方向 | 规则 |
|---|---|---|
| 战斗完成 | 入 | 每完成一场战斗统一到账一次:金额 = 掉落掷定 + 关级掉落加值 + 本战战斗内缓冲,随战斗结束信号携带的 gp_dropped 一笔入账 |
| 战斗完成 | 入 | 每完成一场战斗统一到账一次:金额 = 掉落掷定 + 关级掉落加值 + 本战战斗内缓冲,随战斗结束信号携带的掉落额一笔入账 |
| 商店购买 | 出 | 商店房购买按现行价扣减钱包,余额不足拒绝。价格公式(**v0.13 换代——pool 模式现行**):ceil(基价 × b^(关序−1) × A^(本次进店已购次数)),A=店内递增/b=跨关递增表因子(provisional 1.8/1.1),ceil 向上单一取整(玩家不利向,杜绝「差 1 GP 反复钓」套利角);legacy 固定三件模式(shop=off 对照承载)沿用旧式 = 基价 + 关级价格加成 + 步长 × 已购档数 |
| 冒险收束 | 清零 | 完成 / 失败 / 放弃三种结局都清零;重开后钱包从零开始 |
**战斗内缓冲隔离**:战斗进行中从格面兑付拿到的 GP(GP 袋正向、破财负向——字典归[地图战斗资源](../战斗循环/地图战斗资源.md))不直接进钱包,而是记入战斗域的中间态缓冲;战斗完成时缓冲随掉落掷定与关级加值一并折进 gp_dropped 统一到账。钱包只在战斗边界变动,战斗域自身零钱包状态——这条隔离让战斗结算保持"折算归我、GP 归冒险结构"的既有边界。
**战斗内缓冲隔离**:战斗进行中从格面兑付拿到的 GP(GP 袋正向、破财负向——字典归[地图战斗资源](../战斗循环/地图战斗资源.md))不直接进钱包,而是记入战斗域的中间态缓冲;战斗完成时缓冲随掉落掷定与关级加值一并折进掉落额统一到账。钱包只在战斗边界变动,战斗域自身零钱包状态——这条隔离让战斗结算保持"折算归我、GP 归冒险结构"的既有边界。
**统一冒险加算区**:商店购买的属性提升与奖励节点三选一的属性件记**同一笔**——加算区四轴 {攻击, 生命上限, 护甲等级, 行动点},入战参数 = 基础表值 + 加算区累加,经战斗开始入参的覆盖值通道生效(与生命的上参同一条通道)。购买生命上限时:pool 模式(v0.13——货架件=奖励件本体)只加上限不回当前生命(与三选一件同语义);legacy 模式当前生命与上限同步 +Δ(生命的连带写入口)。属性轴累加源 = 商店购买与奖励节点三选一属性件两类;行动点轴累加源 = 奖励节点三选一 AP 件与商店 AP 件两类(每场战斗开始的首轮行动点基线 +1,语义细节归[行动经济](../排雷循环/行动经济.md)跨冒险加成轴节;~~全冒险获取次数上限最紧 provisional 2 次~~——v0.14 次数上限退役,池消耗即上限,pool 模式下商店购买与三选一共用令牌库,见下)。
掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。**商店货架抽签消费 shop_draw 封闭随机流**(v0.13 扩签——每次抽 3〔进店+每次买后重抽〕恰一 scope,与 reward_pick 分签防取数序互扰)。掉落与价格的现行行值读商店表,数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。
掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。**商店货架抽签消费 shop_draw 封闭随机流**(v0.13 扩签——每次抽 3〔进店+每次买后重抽〕恰一 scope,与三选一抽签分签防取数序互扰)。掉落与价格的现行行值读商店表,数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。
**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代;迭代 20 案D 四级 104 张——池机制事实源=[奇物](奇物.md)「池结构与抽取」节)**:商店货架与奖励节点三选一是**同一个池**——货架候选 = 池∖{GP 袋, 刷新件}∪{凭证} = {攻击, 生命上限, 护甲, 行动点, 回复, 凭证} 六种(GP 袋计价物不自售切边、刷新件不上货架、秘宝件已随同调批出池;凭证与回复随迭代15② 入架——shop.tres pool_shelf 六行);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。禁止两渠道各自私记库存——消费走同一入口写同一令牌库(实现=CurioDeck 件(`core/curio_deck.gd`)的令牌多重集单点——池与判定归件,AdventureRunner 为宿主构造与持有、停点编排留宿主;商店货架/定价/购买/刷新归 ShopSession 件(`core/shop_session.gd`),外部调用面由 LevelRunner 同签名委托承载;kind 维度派生计数 taken(件内成员)承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。**三选一同款付费档(v0.16 2026-10-06 用户令——刷新对齐商店)**:三选一免费双源(刷新件 X/冒险池 Y,机制现行事实源=[秘宝](../跨局循环/秘宝.md)三选一刷新双源节)用尽后 GP 付费刷新——读同 shop_refresh_* 两旋钮(单点调参)、扣同一 GP 冒险账本(LevelRunner.spend_gp 单口)、重抽同 reward_pick 共享池抽签;付费刷计数每停点清零(与进店清零同构);钮常驻选择态、免费与付费两源皆穷=禁用。
**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代;迭代 20 案D 四级 104 张——池机制事实源=[奇物](奇物.md)「池结构与抽取」节)**:商店货架与奖励节点三选一是**同一个池**——货架候选 = 池∖{GP 袋, 刷新件}∪{凭证} = {攻击, 生命上限, 护甲, 行动点, 回复, 凭证} 六种(GP 袋计价物不自售切边、刷新件不上货架、秘宝件已随同调批出池;凭证与回复随迭代15② 入架——shop.tres pool_shelf 六行);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。禁止两渠道各自私记库存——消费走同一入口写同一令牌库(令牌多重集单点:池与判定归该单点持有,各 kind 的派生计数与断点附随件同在这一点上承载)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。**三选一同款付费档(v0.16 2026-10-06 用户令——刷新对齐商店)**:三选一免费双源(刷新件与冒险池两笔,机制现行事实源=[秘宝](../跨局循环/秘宝.md)三选一刷新双源节)用尽后 GP 付费刷新——读同 shop_refresh_* 两旋钮(单点调参)、扣同一 GP 冒险账本(同一扣账入口)、重抽同三选一共享池抽签;付费刷计数每停点清零(与进店清零同构);钮常驻选择态、免费与付费两源皆穷=禁用。
### 关卡大奖励:关卡完成回血 + 专长三选一(钥石退役出大奖励)
@@ -168,9 +168,9 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
兑付次序:部分回血先行(按当刻生命与上限结算),随后呈现三选一**选择停点**——零自动推进,必须玩家点选才兑付,重复兑付被拒(结算受理只算一次语义同族)。选择停点可中断:断点附随件扩「奖励选择态 + 件获取次数计数」,续玩已选件不静默丢失;**候选逐位一致系停点快照承载的实现现状、非验收义务**(不一致不影响玩法——2026-10-01 用户裁决,见「冒险断点」节)。
**奖励节点三选一池(结构层——池机制事实源=[奇物](奇物.md)「池结构与抽取」节,2026-10-11 迭代 20 案D 换代)**:池 = 四级令牌库(案D——普通打底/优秀·稀有做翼/传说惊喜位空集;曝光槽 = 三选一 2 格全池 + 1 格优秀档以上子池,令牌 = 出现次数上限、概率与已拿上限解耦;构成数字与抽取细节全读[奇物](奇物.md)与 `adventure.tres`,provisional 归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批,本节零复制)。**池为冒险级单例**(迭代 10 追加批):关卡内奖励节点的三选一与商店货架共享同一令牌库(同库同摘牌),库存 = 剩余令牌数;消费 reward_pick 封闭随机流(主种子派生、[数据配置](../表现与支撑/数据配置.md)口径),同种子同操作序列同三选项可复放;**MC 策略 316-f**:autoplay 1 级优先度=最稀有(rarest>rare>common)、2 级=最左。断点附随件承载池余库逐枚还原;兑付语义归[关卡节点地图](关卡节点地图.md)。
**奖励节点三选一池(结构层——池机制事实源=[奇物](奇物.md)「池结构与抽取」节,2026-10-11 迭代 20 案D 换代)**:池 = 四级令牌库(案D——普通打底/优秀·稀有做翼/传说惊喜位空集;曝光槽 = 三选一 2 格全池 + 1 格优秀档以上子池,令牌 = 出现次数上限、概率与已拿上限解耦;构成数字与抽取细节全读[奇物](奇物.md)与 `adventure.tres`,provisional 归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批,本节零复制)。**池为冒险级单例**(迭代 10 追加批):关卡内奖励节点的三选一与商店货架共享同一令牌库(同库同摘牌),库存 = 剩余令牌数;消费三选一封闭随机流(主种子派生、[数据配置](../表现与支撑/数据配置.md)口径),同种子同操作序列同三选项可复放;**MC 策略 316-f**:自动游玩 1 级优先度=最稀有(rarest>rare>common)、2 级=最左。断点附随件承载池余库逐枚还原;兑付语义归[关卡节点地图](关卡节点地图.md)。
**秘宝件受同调位约束**(2026-10-09 注:道具件已随迭代 11 第①批 T7 出池——三选一/奖励节点/战斗内渠道现行零道具件投放,下述渠道约束与替换交互无现役触发面,保留作渠道恢复时的契约口径;同调位读数现行事实源=attunement_growth.tres 谓词表实时求值):道具持有上限 = 同调位数。读数源 = 同调位里程碑谓词表 attunement_growth.tres 实时求值(0 起步、硬上限 3、增长 = 通关次数 + Boss 击杀数——同调实施批 2026-10-03 落地,过渡读数源 transitional_sync_slots 已顺带退役;原提案八拍板 git 57da58c 已退役)。获取渠道约束形态(迭代 10 过渡承载):三选一道具件超位触发**替换交互**(现有件与新件 N+1 选 N——丢任一原有件腾位收新,或放弃新件不获取;终态恒 ≤ 同调位);奖励节点与战斗内渠道超位**不授予**(候选拒绝 + 掉落沉没,逐行归因留痕)——停点渠道全覆盖的替换交互随同调实施批统一完成(原待办清单条目已随完成移除)。
**秘宝件受同调位约束**(2026-10-09 注:道具件已随迭代 11 第①批 T7 出池——三选一/奖励节点/战斗内渠道现行零道具件投放,下述渠道约束与替换交互无现役触发面,保留作渠道恢复时的契约口径;同调位读数现行事实源=attunement_growth.tres 谓词表实时求值):道具持有上限 = 同调位数。读数源 = 同调位里程碑谓词表 attunement_growth.tres 实时求值(0 起步、硬上限 3、增长 = 通关次数 + Boss 击杀数——同调实施批 2026-10-03 落地,过渡读数源已顺带退役;原提案八拍板已退役)。获取渠道约束形态(迭代 10 过渡承载):三选一道具件超位触发**替换交互**(现有件与新件 N+1 选 N——丢任一原有件腾位收新,或放弃新件不获取;终态恒 ≤ 同调位);奖励节点与战斗内渠道超位**不授予**(候选拒绝 + 掉落沉没,逐行归因留痕)——停点渠道全覆盖的替换交互随同调实施批统一完成(原待办清单条目已随完成移除)。
末关完成不发三选一与部分回血:没有下一战可受益——只做冒险完成结算,末关 Boss 钥石照常入账。设计意图:清库之后才兑付、末关不兑付,都是从「奖励只朝下一战生效」这一条推出来的。
@@ -233,7 +233,7 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
|---|---|---|
| 上游 | [难度曲线与Boss](难度曲线与Boss.md) | 逐关关卡完成率带是序列难度排布的输入;三关序列均已生效(地精洞窟→幽暗地域→灰矮人矿城),无未落前置;大奖励与恢复点数值的联动复验归它 |
| 上游 | [数据配置](../表现与支撑/数据配置.md) | 关卡表与冒险表(单行全局)的承载与统一读取,本文是两表的内容与回填责任方;主种子的注入入口(随机服务) |
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 主角战斗入参覆盖值通道(本文供生命与上限);关卡收束接口由关卡节点地图收束点调用,本文零直调;战后生命终值与本战 GP 掉落(gp_dropped)经上报读回 |
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 主角战斗入参覆盖值通道(本文供生命与上限);关卡收束接口由关卡节点地图收束点调用,本文零直调;战后生命终值与本战 GP 掉落(掉落额)经上报读回 |
| 下游 | [关卡节点地图](关卡节点地图.md) | 接口契约四件套 + 收束点语义,本文是定义源,也是其文档的强制输入 |
| 下游 | [元进度](../跨局循环/元进度.md) | 冒险摘要承载钥石入账合计(元进度是消费方);冒险开始参数面预留(未来跨局加成在此注入,MVP 空参数) |
| 下游 | [存档序列化](../表现与支撑/存档序列化.md) | 快照契约域单位 = 冒险(主种子 + 随机流计数器);摘要含主种子供复盘;断点域 `breakpoint` 内容 owner = 本文(迭代 8 卡①——七字段+附随件,见「冒险断点」节) |
@@ -258,16 +258,16 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
## 调参表
全部数值存配置表、经统一读取口读入,代码里零数值字面量;表本体与读取机制归[数据配置](../表现与支撑/数据配置.md)。已锁字段以 `data/schemas/level_table.gd`/`adventure_table.gd` 声明与对应 `.tres` 行值为准。
全部数值存配置表、经统一读取口读入,代码里零数值字面量;表本体与读取机制归[数据配置](../表现与支撑/数据配置.md)。已锁字段以关卡表与冒险表的字段声明及对应表行为准。
| 参数 | 域 | 现行值 | 说明 |
|---|---|---|---|
| 关卡行集(关数与图的参数档) | 关卡表逐关行 | 三关现行(图由生成器按参数档产出,见[关卡节点图生成](关卡节点图生成.md)) | 加关 = 内容批(参数档 + 敌我数值 + 新元素同批);确定性 = 固定种子+固定参数档(首局教学与存档复现的固定图由此承载,无兜底模板) |
| 恢复比例 | int [0,100],逐关行 | 见 level.tres rest_heal_pct(provisional) | 恢复点回复生效上限的百分比;随上限加成同步放大;100 = 恢复点变全满泉,0 = 死节点 |
| 上限提升 | int [0,999],冒险表逐件列 | adventure_table bonus_attack_flat / bonus_max_hp_flat / bonus_armor_flat(provisional) | 三选一属性件各自兑付值;值锚与上调约束归难度曲线与Boss |
| 三选一件值(其余) | int [0,999],冒险表 kind×tier 列族 | adventure_table curio_value_*(GP 袋 / AP 件 / 恢复 / 刷新,provisional) | 令牌件值读表;道具件授予件数随秘宝件出池退役 |
| 上限提升 | int [0,999],冒险表逐件列 | adventure_table value_segment.curio_value_attack/max_hp/armor_{common,excellent}(provisional) | 三选一属性件各自兑付值;值锚与上调约束归难度曲线与Boss |
| 三选一件值(其余) | int [0,999],冒险表 kind×tier 列族 | adventure_table value_segment.curio_value_*(GP 袋 / AP 件 / 恢复 / 刷新,provisional) | 令牌件值读表;道具件授予件数随秘宝件出池退役 |
| ~~三选一权重~~(v0.14 退役——令牌池档位配额即分布、无独立权重 knob) | — | — | E-S 加权洗牌不放回抽样权重随 TP0 令牌池换代退役 |
| 件次数上限 | ~~int [0,99],冒险表逐件键~~(v0.14 退役——池消耗即上限) | adventure_table token_* 配额列族(TP0 令牌池——kind×tier 配额) | 达上限件先剔出牌库永不再现 → 池耗即枯竭 |
| 件次数上限 | ~~int [0,99],冒险表逐件键~~(v0.14 退役——池消耗即上限) | adventure_table quota_segment.curio_quota_*(TP0 令牌池——kind×tier 配额) | 达上限件先剔出牌库永不再现 → 池耗即枯竭 |
| 关卡完成回血比例 | int [0,100],冒险表单行全局 | adventure_table clear_heal_pct(provisional 20) | 关卡完成部分回血回复生效上限的百分比;封顶上限不回满 |
| ~~同调位过渡键~~(已退役——迭代 11 第①批 T7,过渡键随表退役) | — | 同调位读数源 = attunement_growth.tres 谓词表实时求值(slot_cap 3) | 切换义务已随 2026-10-03 同调实施批清偿 |
| Boss 钥石掉落 | int [0,999],meta 表逐关行 | meta.tres boss_keystone 首杀 3 / 复杀 2(provisional) | 逐杀实时入账;精英怪掉 1 预留;消费方归元进度 |
@@ -287,7 +287,7 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜
- 生命一致性:每战开始的上参 = 生命当前值与生效上限;战后生命 = 上报终值;三处写入口均封顶在 [0, 生效上限]。
- 恢复点:回复 = floor(生效上限 × 恢复比例 ÷ 100) 后封顶;满血腿回复 0 合法。
- 大奖励覆盖:三选一兑付后首战的入参 = 基础表值 + 加算区累加含选中件(覆盖值通道);AP 件兑付后每场战斗开始首轮行动点基线 +1。
- 抽签封闭:三选一掷 3 消费 reward_pick 封闭流,同种子同操作序列同三选项;单次零重复;令牌耗尽快即枯竭、不再出现在候选(池消耗即上限)。
- 抽签封闭:三选一掷 3 消费三选一封闭流,同种子同操作序列同三选项;单次零重复;令牌耗尽快即枯竭、不再出现在候选(池消耗即上限)。
- 同调位约束:道具终态持有恒 ≤ 同调位(谓词表实时求值);超位时停点渠道触发替换交互(丢旧收新 / 放弃新件两路),战斗内渠道沉没不入;奖励节点渠道同受约束。(现行零道具件投放——约束保留作契约,见「秘宝件受同调位约束」节注。)
- 钥石逐杀:Boss 击杀后钥石累计增量 = boss_keystone 行值(首杀/复杀两档);大奖励面钥石入账零残留。
- 收束次序:关卡完成宣布到达时库存已被关卡节点地图先行清空;重复的关卡结束宣布被拒绝,状态与摘要零变化。
+41 -17
View File
@@ -11,23 +11,25 @@
>
> **兑付频率(先看这条,它是三条渠道的共同前提)**:奇物**不是打一场战斗给一份**。关内的普通战斗节点打完了什么也不给;一关之内奇物**只兑付一次**,触发点是**关底 Boss 被击败**——而按 [冒险结构](冒险结构.md) 的定义,打掉关底 Boss 恰好就等于"通过这一关",这两件事是同一个时刻,不是一条链上的两步。
>
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地)。本文件是奇物的唯一定义文档。
> 状态:**已实现**(2026-10-04 双层 BD 实施批落地);现行为 v1.2。本文件是奇物的唯一定义文档。
>
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `game/data/tables/feats.tres` 与 `game/data/tables/adventure.tres`。
> 数值纪律:本文件只写概念、取值域与指路;每件给多少、效果是什么一律不进正文,事实源是 `data/tables/feats.tres` 与 `data/tables/adventure.tres`。
## 专长(重设计·基础框架 v1)
> 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已随迭代 20 专长实装批落地(2026-10-11):`feats.tres` 29 行=基础 27+迁入 2,行为与数据行 1:1。**
> 本节是专长分类的现行事实源(2026-10-07 用户终审认同写入文档)。件表为设计声明——数值全 provisional 归调参批;命名全为代号,正式命名归用户。**新框架已实装:`feats.tres` 30 行=基础 27+迁入 2+规则豁免 1,行为与数据行 1:1。**
### 设计原则
1. **触发器句式**:每件专长的效果声明为「当 X 时 Y」(on / when / do 三字段,词表=效果引擎封闭集)。件的性格来自它挂在哪个事件上;combo 靠事件共享自然涌现——不设流派标签、不设档位机制。
2. **事件族组织**(设计侧分组工具,不上卡面):开格系 / 封印系 / 踩雷系 / 节奏系 / 战斗系五族。族名禁上玩家可见面(命名铁律)。
2. **事件族组织**(设计侧分组工具,不上卡面):开格系 / 封印系 / 踩雷系 / 节奏系 / 战斗系 / 换图系六族。族名禁上玩家可见面(命名铁律)。
3. **准入校验**:代价与收益须同账可核算——「纯代价件」禁止出生;契约件的代价必须一句话讲得清(契约不可怕,糊涂账才可怕)。
4. **信息红线**:禁止任何效果让数字说谎(信息规则不豁免);揭示类效果只揭真格。
5. **防兑换环**:互相转换的通道必须有损耗或每轮上限;单向通道优先。
6. **基座先行**:流派先确定基座专长——通过改变资源来源、动作成本或频率、动作结算、动作之间的关系,让原本偶尔用于止损的动作成为可以持续使用的主要打法。后续专长再围绕这个新行为提供输出、生存或资源收益;泛用数值增长不能代替基座。
7. **行为改变检验**:基座必须能回答「拿到前后操作有何不同、新行为靠什么资源持续、后续专长为何依附这个新行为」。只是同一操作后的结算数字变大,不算形成新流派。
> **落行注记(迭代 20 反哺写入,2026-10-12 追认⑤)**:「每第 N 次 / 积满触发」的数据行条件写 **N−1**——效果引擎 dispatch 时 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 次准点触发,单元测试钉住;新件落行照此口径。
### 基础件表(27 件,五族 + 跨族桥 + 契约尖点)
@@ -93,7 +95,29 @@
| 代号 | 契约 | 账 |
|---|---|---|
| 血税 | 本局:踩雷 −2 最大生命;标中 +1 攻击(**均下一战起生效**——战末汇入冒险加算区) | 双向都痛都爽,一句话讲得清;双向同账、本局累积、冒险收束清零。max_hp/攻击系战界重建属性,即时改会被重建冲掉,故滞后一战兑付(2026-10-12 追认⑨回写;卡面 desc 同步补「下一战起生效」) |
| 血税 | 本局:踩雷 −2 最大生命;标中 +1 攻击(**均下一战起生效**——战末汇入冒险加算区) | 双向都痛都爽,一句话讲得清;双向同账、本局累积、冒险收束清零。生命上限/攻击系属战界重建属性,即时改会被重建冲掉,故滞后一战兑付(2026-10-12 追认⑨回写;卡面 desc 同步补「下一战起生效」) |
### 换图系(7 件,设计定稿,待实现)
换图系把「弃图刷新」从坏图逃生改成主动构筑行为。两件基座分别解决 RAP 的来源与价格;后续专长让成功刷新产生输出、生存或资源收益。七件不要求一次拿齐,不设强制先拿基座、集件标签或集件奖励。
| 层 | 代号 | 规则 | 行为职责 |
|---|---|---|---|
| 基座 | 掘进储能 | 每次付费挖格后额外获得 RAP;安全格与危险格同口径 | 持续挖图也能准备刷新,不再必须少挖停手 |
| 基座 | 折价换图 | 弃图刷新的 RAP 成本减半 | 同一份储备支持更多刷新 |
| 输出 | 弃图轰击 | 成功刷新后,在本轮战斗段对全体存活敌人造成一次伤害 | 刷新成为直接进攻行为 |
| 输出 | 换图蓄势 | 成功刷新攒层,层数有上限;下一次攻击附加全部层数对应的伤害后清零,战斗结束清空 | 连续换图蓄成一次单点爆发 |
| 生存 | 换图庇护 | 成功刷新后,在本轮授予段获得护盾 | 用刷新预防后续伤害 |
| 生存 | 弃图休整 | 成功刷新后,在本轮授予段回复生命,封顶生命上限 | 放弃当前图换取恢复 |
| 资源 | 弃图回收 | 成功刷新后,按旧图探索进度在本轮授予段回收清图奖励 | 先探索再主动弃图变现 |
共同规则:
- **双基座叠加**:掘进储能与折价换图可以同时持有,产 RAP 与成本减半分别生效;每轮刷新次数上限继续限频,不设互斥规则。
- **成功才触发**:后续五件只认成功换图;刷新被拒绝时不产生效果。同一轮多次成功刷新按发生顺序逐次登记。
- **轮末兑付**:刷新不新增结算段。群伤、护盾、回血与回收按[战斗结算](../战斗循环/战斗结算.md)的既有段序兑付,刷新瞬间不直接改这些战斗结果。
- **回收看旧图**:弃图回收读取被放弃旧图的探索进度,不把旧图或新图判为清图;具体口径归[地图战斗资源](../战斗循环/地图战斗资源.md)。
- **基线不变**:没有这些专长时,RAP 来源、刷新成本、清图奖励与刷新结果保持原规则。数值与稀有度归 `feats.tres`,落表前本节只作设计声明。
### 迁入登记(秘宝域迁出件;实装批落地后并入件表)
@@ -104,11 +128,11 @@
惊巢反制=退役进素材库(不迁入)。
**资源类迁出件(2026-10-18 用户裁)**:完美保险自资源类迁入专长——理由:它给的是**规则豁免**而非可调动的余量(§1.1 判据「给规则的是专长」),效果=本场首次封印落空不计入完美封印判定。旧「资源类成员」归类系 2026-10-08 框架批写入、无用户裁断支撑,随本次改判注销。专长行已于迭代 31 卡② 落表(合并 `3e076e22`):`feats.tres` 单行 `when counter(insurance_used) lt 1` 承载「本场首次」,效果类型 `forgive_seal_miss` 随引擎扩签;稀有档配额随净删重裁下调,现行值读 `game/data/tables/adventure.tres`。
**资源类迁出件(2026-10-18 用户裁)**:完美保险自资源类迁入专长——理由:它给的是**规则豁免**而非可调动的余量(§1.1 判据「给规则的是专长」),效果=本场首次封印落空不计入完美封印判定。旧「资源类成员」归类系 2026-10-08 框架批写入、无用户裁断支撑,随本次改判注销。专长行已于迭代 31 卡② 落表:`feats.tres` 单行 `when counter(insurance_used) lt 1` 承载「本场首次」,效果类型 `forgive_seal_miss` 随引擎扩签;稀有档配额随净删重裁下调,现行值读 `data/tables/adventure.tres`。
### 扩签登记(迭代 20 实装已承载——2026-10-12 追认④写入文档换代)
- **落地结果**:事实候选 10 条中 6 条落 FACTS 词位(边缘格判定/空白连锁判定/单次伤害量/被挖格当时有封印印记/雷罚伤害量/生命比例),4 条改道 adventure 域计数器(类型集合计数器/本轮挖格花费/本轮开格数/本轮踩雷数计数器——跨事件累积量走计数器+value_event 比较,不占 fact 词位,T0 定架构);停手事件已落(dig_stop);实现随裁补位=battle_won 事件(战地包扎所需,登记未列)、mod_eq 算子(周期触发)、hp_ratio(百分比 int 口径)等 3 项事实、新效果类型 7 项。
- **落地结果**:事实候选 10 条中 6 条落事实词位(边缘格判定/空白连锁判定/单次伤害量/被挖格当时有封印印记/雷罚伤害量/生命比例),4 条改道 adventure 域计数器(类型集合计数器/本轮挖格花费/本轮开格数/本轮踩雷数计数器——跨事件累积量走计数器+value_event 比较,不占 fact 词位,T0 定架构);停手事件已落(dig_stop);实现随裁补位=battle_won 事件(战地包扎所需,登记未列)、mod_eq 算子(周期触发)、hp_ratio(百分比 int 口径)等 3 项事实、新效果类型 7 项。
- **连带**:净化工兵退役,rewrite_board 效果类型自引擎除名(扩签「暂缓:清图事件」不受影响,继续挂起)。
- **暂缓**:清图事件(随清图奖励题)。
@@ -122,13 +146,13 @@
### 凭证件表(新 3 + 留任 2;代号制命名归用户;稀有度档、配额与面值数值归调参批)
> **2026-10-18 用户裁削两件**:**同调电池**删除——秘宝是「局外同调件」、冒险中零更换途径,与 `cf04bd66` 撤换装券/删回能器同一原则(且 UI 层从未接线选件入口);**完美保险**迁出资源类改归专长——它给的是规则豁免,不是余量,见 §1.4 判据。两件原职责账(秘宝充能/完美资格)同步自 §1.3 账清单注销。
> **2026-10-18 用户裁削两件**:**同调电池**删除——秘宝是「局外同调件」、冒险中零更换途径,与撤换装券/删回能器同一原则(且 UI 层从未接线选件入口);**完美保险**迁出资源类改归专长——它给的是规则豁免,不是余量,见 §1.4 判据。两件原职责账(秘宝充能/完美资格)同步自 §1.3 账清单注销。
| 代号 | 写进哪笔系统资源(寿命档) | 一句话 | 备注 |
|---|---|---|---|
| 刷新券 | 刷新存量账(冒险级) | 存量刷新次数 +1 | 语义=存量加仓(已裁);实现依赖刷新件写错账修复(同批) |
| 储备金袋 | 储备池 RAP(节点级) | 储备池 +N | 给停手/刷新雷图经济加燃料;战斗结束清零系既有语义,兼容 |
| 时沙 | 下一战首轮 AP 队列(拾取挂账,兑付即消尽) | 下一战首轮 AP +2 | 一次性节奏件;与「AP 上限+1」(属性轴)不撞——一个改池子、一个改上限(落法=hero_ap_bonus 既有通道,2026-10-12 追认⑦回写;拾取时战斗在途同样等下一战首轮) |
| 时沙 | 下一战首轮 AP 队列(拾取挂账,兑付即消尽) | 下一战首轮 AP +2 | 一次性节奏件;与「AP 上限+1」(属性轴)不撞——一个改池子、一个改上限(落法=AP 加成既有通道,2026-10-12 追认⑦回写;拾取时战斗在途同样等下一战首轮) |
| 小 GP 袋 | GP 钱包(冒险级) | (留任)+20 / +50 | 计价物不自售维持(不进商店) |
| 应急口粮 | 生命账(冒险级) | (留任)回复 +6 | 在商店内(默认原则) |
@@ -230,7 +254,7 @@
**补给类内部的异质性(统一表述)**:凭证全部即时消尽——不存在「持有凭证」这种状态;异质性不在凭证上,而在**被写入的系统资源的寿命**上:GP 钱包 / 生命 / 刷新存量 = 冒险级,储备池 = 节点级,时沙暂记 = 至下一战首轮兑付(系统资源清单对齐「凭证件表(新 3+留任 2)」节;「AP 上限」已归属性值提升轴,见 1.2)。
- 原在册的「秘宝充能」「完美资格」两笔随 2026-10-18 两件裁削注销——`attune_charges` 账本本身仍为秘宝基础设施保留(迭代 11,主动件充能每冒险重置),本次只删奇物侧写它的入口。
- 原在册的「秘宝充能」「完美资格」两笔随 2026-10-18 两件裁削注销——充能账本本身仍为秘宝基础设施保留(迭代 11,主动件充能每冒险重置),本次只删奇物侧写它的入口。
- 完美资格的豁免走**订阅式自有累加账**:授予段在 `seal_missed` 命中时累加豁免数,判定取「误标总数 − 豁免数」得有效数;**不改写误标账本身、不拦截事件**(迭代 31 卡② 契约 2/3)。
- 登记此节防止把凭证当成会持续生效的东西去结算。
@@ -263,8 +287,6 @@
**为什么两层都要有**:分类名在卡面上不出现,不等于玩家对它没有概念——帮助手册、百科条目、成就名称三处一定会用到它(「首次集齐 4 件专长」「三件属性值提升」),效果描述里出现指代时也需要名字(如「本项属专长」类归类指代)。所以它必须是一个**玩家读得懂、读一遍就懂**的正式名词,不能是内部描述或自造词。
**代码侧词族对应**(本批只做命名对齐,代码改动见需求卡片):顶层 `curio_*`(奇物总称)/ 专长 `feat_*` / 属性值提升 `asi_*` / 资源 `res_*`。`reward_*` 已被既有标识符占用(`reward_pick` / `reward_node` / `reward_preview`),**禁止使用**。
### 2. 渠道轴:与分类的交叉
三个渠道各只取走分类的一部分。这张表是奇物概念的第二个维度——它和分类轴互不决定,改其中一根不必动另一根。
@@ -433,12 +455,12 @@
| 参数 | 取值域 | 事实源 |
|---|---|---|
| 专长定义行(标识 / 显示名 / 稀有度 / 效果说明 / 订阅行〔订阅哪个事件 on / 什么条件 when / 什么效果 do〕) | 各字段封闭枚举,行键白名单封闭;事件 / 效果类型 / 条件事实 / 算子四词表封闭(效果引擎单一事实源);稀有度档=新四级(普通/优秀/稀有/传说),schema 字段双脱钩(字段在、设计面按档配比加权) | `game/data/tables/feats.tres` |
| 专长凭证(出现概率 / 配额 / 限购次数 / 保底计数阈值) | 整数,四参数全 provisional | 已入表(迭代15② 凭证行——`shop.tres` + `adventure_table.gd`) |
| 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | `game/data/schemas/adventure_table.gd` |
| 专长定义行(标识 / 显示名 / 稀有度 / 效果说明 / 订阅行〔订阅哪个事件 on / 什么条件 when / 什么效果 do〕) | 各字段封闭枚举,行键白名单封闭;事件 / 效果类型 / 条件事实 / 算子四词表封闭(效果引擎单一事实源);稀有度档=新四级(普通/优秀/稀有/传说),schema 字段双脱钩(字段在、设计面按档配比加权) | `data/tables/feats.tres` |
| 专长凭证(出现概率 / 配额 / 限购次数 / 保底计数阈值) | 整数,四参数全 provisional | 已入表(`shop.tres` 凭证行 + 冒险表凭证三计数行) |
| 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | 冒险表结构声明 |
| 补给种类 | 种类现行事实源=「凭证件表(新 3+留任 2)」节;凭证已入表(迭代15②) | 同上 |
| 稀有度档位 | 封闭四级(普通 / 优秀 / 稀有 / 传说);配额是结构锚,改总量须重新拍板而非调参 | 同上 |
| 各分类种类 × 档位的配额与值 | 整数 | `game/data/tables/adventure.tres` |
| 各分类种类 × 档位的配额与值 | 整数 | `data/tables/adventure.tres` |
| 专长获取的全局限量 | 整数(一局内可获得的次数上限) | 同上 |
| 一次三选一的候选展示数 | [1,6] | 同上 |
@@ -456,6 +478,8 @@
10. 一个渠道一旦确定要出某个分类,该分类的成员就都在它的池子里——"渠道出哪些分类"不受这条约束,那是渠道轴的定位。
11. 凭证拿到即开,不进持有集,玩家无法囤积——选中或买下的同一时刻就开专长三选一。
12. 凭证带有计步保底:连续 5 次三选一展示未出现则第 6 次必出;计数跨奖励节点与商店共享,刷新后的重新展示计入次数,限购用满后保底不再触发。
13. 被称为流派基座的专长必须能说明拿到前后的操作差异与新行为的持续资源来源;只有同一操作后的泛用数值增长,不得登记为基座。
14. 换图系两件基座同时持有时都生效;后续五件只在成功刷新后触发,无专长基线不变;弃图回收按旧图探索进度结算,同一旧图至多回收一次。
> 实装批落地前,现役代码按旧 27 件专长目录与旧资源成员在同框架下运行;新成员口径(27 件基础件表 / 凭证件表)自实装批起验。
@@ -1,7 +1,7 @@
# 难度曲线与Boss
> 一句话:难度曲线、期望值带与 Boss 设计
> 状态:已定稿(自旧体系迁移);现行为 v1.1
> 状态:已定稿(自旧体系迁移);现行为 v1.2
## 设计目的
@@ -57,7 +57,7 @@
| 关内坡度(节点深度) | 同敌种数值平缓上移,深度也可表达为敌人成群 | 数量是比单体质变更优先的深度表达:保持技能池 EV 读数不变(权重即旋钮、EV 重锚顺带登记)、总量受控 |
| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统,[棋盘生成](../排雷循环/棋盘生成.md)只按表生成。精英怪设定已登记预留(迭代 10 奖励重构批——与其他普通小怪同一立绘 + 附加特征标记 + 数值更强;视觉特征三件 = 名称前后缀「精英·」+ 底座光环 + 描边变色,色板合规与样张闸义务随怪物设定批实装) |
**结论**:关内坡度 = 每深一档同敌种基准上移,或按"深度也可表达为敌人成群"改由数量承载——单体生命随深度的具体走向以 `enemy.tres` 行值为准(数量优先于单体;s_hp 线性式为建模工具,现行行值不承诺随深度单调上移);关间台阶 = 相邻关同敌种基准乘 1.3–1.6(域语义如上;s_hp / k_L 现行行值读 `enemy.tres`,域见 `data/schemas/enemy_table.gd`)。
**结论**:关内坡度 = 每深一档同敌种基准上移,或按"深度也可表达为敌人成群"改由数量承载——单体生命随深度的具体走向以 `enemy.tres` 行值为准(数量优先于单体;s_hp 线性式为建模工具,现行行值不承诺随深度单调上移);关间台阶 = 相邻关同敌种基准乘 1.3–1.6(域语义如上;s_hp / k_L 现行行值读 `enemy.tres`,取值域归敌人表字段定义)。
```
hp_D = round( hp_0 × (1 + s_hp × D) ) s_hp ∈ [0.05, 0.20]
@@ -111,7 +111,7 @@ MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的
| 带 | 定稿口径 | 含义 |
|---|---|---|
| 通关率带 | 口径已换代·现行带 = 四带甲案(2026-10-03 用户拍板):带 A 白板完美执行战役全通率 [0%,10%] / 带 B 白板第一关通过率 [50%,85%] / 带 C 营地满成长战役全通率 [30%,60%](预立) / 带 D 响应曲线每雷边际 ≤15pp·S 形——旧普通档 L1 [35,55] / L2 [10,15] 与完美档 [35,65] 正式作废,全文见本节末「带换代」条。实证:直入口 L2 普通 5% vs 冒险条件口径 L2 90% 同期并存〔「营地检查点」= balance_verifier 营地循环判定段旧称(现名营地循环战役)——2026-09-30 词源考证完成,见审计报告 git 提交 c095916 拍板点 9(已按生命周期条款退役);与冒险结构「没有检查点」铁律的字面相撞就此解除〕 | 四带按类型基准锚定、每条带均有外部锚点:带 B 管首关 = 过滤器不是墙;带 A 管白板全通的顶尖难度;带 C 留元进度爬升空间(满成长实装后启用);带 D 管响应曲线无悬崖 |
| 通关率带 | 口径已换代·现行带 = 四带甲案(2026-10-03 用户拍板):带 A 白板完美执行战役全通率 [0%,10%] / 带 B 白板第一关通过率 [50%,85%] / 带 C 营地满成长战役全通率 [30%,60%](预立) / 带 D 响应曲线每雷边际 ≤15pp·S 形——旧普通档 L1 [35,55] / L2 [10,15] 与完美档 [35,65] 正式作废,全文见本节末「带换代」条。实证:直入口 L2 普通 5% vs 冒险条件口径 L2 90% 同期并存〔「营地检查点」= 数值验证工具营地循环判定段旧称(现名营地循环战役)——2026-09-30 词源考证完成,见审计报告拍板点 9(已按生命周期条款退役);与冒险结构「没有检查点」铁律的字面相撞就此解除〕 | 四带按类型基准锚定、每条带均有外部锚点:带 B 管首关 = 过滤器不是墙;带 A 管白板全通的顶尖难度;带 C 留元进度爬升空间(满成长实装后启用);带 D 管响应曲线无悬崖 |
| 关卡完成余量带 | 关卡完成者关卡完成时剩余生命的 p90 / p10 按现生命轴上限百分比表达(压缩前绝对血量带已作废;界值重标随验收批) | **关卡完成余量** = 关卡完成瞬间还剩多少血。余量太肥说明赢得太从容,压生存侧;太瘦说明"剩一两滴血惨胜"成常态,抬治疗供给或恢复点收益 |
| 第一关形态带 | 教学位绿档 + 中段黄档 + Boss 黄档不红 | **形态带**管一关内各战三档判定的排布形状:第一战必须稳过,中段有输的可能,Boss 战紧但不许是配置事故 |
@@ -123,7 +123,7 @@ MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的
**迭代 24 敌人表内容修批读数(只测不判——2026-10-14 反哺写入批登记,用户裁「全部按呈报写入文档」;数值终值归 M4 数值批)**:同种子配对 seed0=9000(沿用迭代 23 配对种子)改前/改后各 40 局×both——①**N2 普通敌技能投放**(毒吐/盾击两新行六池)后 perfect L2 通关 8/20→13/20(+25pp)、L3 全通 6/20→10/20(+20pp)——方向符合预期(L1 减压后更健康抵达 L2/L3),量级低于二档配对判读带 ~30pp,倍率 provisional 归 M4;normal 档基率地板(0/20)无判读力;②**N3 敌方攻击类型轴**(20 行全填 normal+敌方兑付路径克制消费)后全局平移 = 主角穿重甲受伤点数 −1 / 魔法护甲 +1 / 轻甲 0(V5 冒烟同种子三甲型配对实测单发恒 ±1:heavy 2==light 3−1 · ward 4==light 3+1);taken 中位 normal 29.5→27.5、perfect 76.5→97.0——后者系**构成效应**(全通 6→10 局通关更深自然累计更多承伤),非单发异常;③N1 (1,2) 降档(hp 50→22·ac 3→2)后 perfect L1 通关 19/20→20/20——5 倍墙消除方向符合预期。判读与带重锚随 M4 数值批,本注记只登记不判读。
**判读口径同步(迭代 5 基线重测批,2026-09-28)**:通关率带的测量口径 = 冒险条件通关率(打通 k−1 关才到达第 k 关,balance_verifier 冒险段派生;单关直入口口径已随 2026-09-23 拍板退役)——带值沿用 2026-09-20 分带修正(普通档 L1 [35,55] / L2 [25,45]、完美档 [35,65];迭代 5 计划书 C4 冻结:改带=用户拍板)。上方行 115 的「带退役待重锚、重锚前只测不判」注记按本条同步理解:迭代 5 基线重测已按现行带对冒险条件口径判读(读数报告见 git 提交 e55dc10,已按生命周期条款退役);是否在新口径上重取带值,归迭代 5 出口判读由用户拍板,本批不动带数字。
**判读口径同步(迭代 5 基线重测批,2026-09-28)**:通关率带的测量口径 = 冒险条件通关率(打通 k−1 关才到达第 k 关,由数值验证工具冒险段派生;单关直入口口径已随 2026-09-23 拍板退役)——带值沿用 2026-09-20 分带修正(普通档 L1 [35,55] / L2 [25,45]、完美档 [35,65];迭代 5 计划书 C4 冻结:改带=用户拍板)。上方行 115 的「带退役待重锚、重锚前只测不判」注记按本条同步理解:迭代 5 基线重测已按现行带对冒险条件口径判读(读数报告已按生命周期条款退役);是否在新口径上重取带值,归迭代 5 出口判读由用户拍板,本批不动带数字。
> **⚠ 带失效待重锚(2026-10-01 数值修复批)**:模拟档位重构(普通档 v2 新手画像→v3=完美策略+受控失误注入;完美档补元游戏选路与商店购买)+数值三刀(hero max_hp 20→26/直伤雷 4/5/5→6/8/8/L1 系敌 atk 2→1)后,上方三条验收带的测量基准已失义——旧普通档=比真人弱 20 倍的新手画像(真人实证推翻),旧带值系照坏尺子校定。新尺读数(N=100/200)与调优轨迹见 `数值修复批读数(已退役,git 历史可查)`;~~新带值须新尺重测后用户拍板,重锚前只测不判~~(已由下行带换代完成)。
@@ -175,7 +175,7 @@ GP 与商店房随 2026-09-20 GP+商店房批落地,给难度引入第三族
公式语义与 GP 归[冒险结构](冒险结构.md)(GP 节),商店房编排归[关卡节点地图](关卡节点地图.md);行值读 `data/tables/shop.tres`,全部 provisional 归本文曲线调参批。
**分层验收 defer(2026-09-20 用户拍板成文)**:按"商店存在与否"分层的通关率验收——MC 双格(shop=off 现行基线 / shop=on 购买画像)、shop=on 的带平移量、商店属性提升的换算率登记(换算率强条目纪律同类)——**全部随曲线调参批执行**,与「基线通关率越带」同窗(原「L2 Boss 承伤微越带」「大奖励数值随难度曲线联动」两待办清单条目已完成移除——前者 2026-09-28 读数自愈、后者随迭代 10 大奖励退役失义)。执行前 shop=off 续用现行带判读,shop=on 读数只登记不判读、禁止用 shop=off 读数外推 shop=on。L3 首遇绿档冒烟断言仍按成长代理档口径,不因商店批改判。**v0.22 注记(迭代 11 第②批)**:shop=off 对照 = legacy 固定三件模式(MC 逐项比对 off_n60 逐位,迭代 11 第②批 T5);shop=on 首锚 = 共享池货架面 N=60 双策略(`runsum_m4iter11b_t5shopon_n60_20261003.jsonl`,attune=off 同种子配对)——带平移判读仍归调参批,终局池读数(pool_stock)随行只测不判。
**分层验收 defer(2026-09-20 用户拍板成文)**:按"商店存在与否"分层的通关率验收——MC 双格(shop=off 现行基线 / shop=on 购买画像)、shop=on 的带平移量、商店属性提升的换算率登记(换算率强条目纪律同类)——**全部随曲线调参批执行**,与「基线通关率越带」同窗(原「L2 Boss 承伤微越带」「大奖励数值随难度曲线联动」两待办清单条目已完成移除——前者 2026-09-28 读数自愈、后者随迭代 10 大奖励退役失义)。执行前 shop=off 续用现行带判读,shop=on 读数只登记不判读、禁止用 shop=off 读数外推 shop=on。L3 首遇绿档冒烟断言仍按成长代理档口径,不因商店批改判。**v0.22 注记(迭代 11 第②批)**:shop=off 对照 = legacy 固定三件模式(MC 逐项比对 off_n60 逐位,迭代 11 第②批 T5);shop=on 首锚 = 共享池货架面 N=60 双策略(同调关闭同种子配对)——带平移判读仍归调参批,终局池读数(pool_stock)随行只测不判。
### Boss 设计:食人魔酋长与关底血量锚
@@ -220,13 +220,13 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
| 封印溢价系数 δ ↔ 完美乘数 p_perfect ↔ ap_per_round | [战斗结算](../战斗循环/战斗结算.md)、[行动经济](../排雷循环/行动经济.md) | 封印激励三件套同窗检算(防封印 rush 复发:δ 与 ×2 叠加通胀、AP 吞吐上限)——任一变动须重跑双画像封印率读数(2026-10-05 判式换代批新增) |
| η 读数 ↔ 模拟画像定义 ↔ 可推理率 ι ↔ min_safe_count/生成尝试上限 | sim 档位、[棋盘生成](../排雷循环/棋盘生成.md) | η 是画像条件量:画像重构即锚漂移,重锚顺带;可推理率三元组既有联动扩一员(2026-10-05 判式换代批新增) |
| β 带端点 β_lo/β_hi ↔ D_ref ↔ hero max_hp ↔ AC 折算档(5pp 系数) | [战斗结算](../战斗循环/战斗结算.md) | 带是 D_ref 的函数:换锚(D_ref/max_hp)须全带重推;AC 档值变更改 5pp 系数,同罪(2026-10-05 判式换代批新增) |
| 直伤雷三行(6/8/8) ↔ 正向同轴行(heal_minor/pierce_strike/magic_bolt) | [地图战斗资源](../战斗循环/地图战斗资源.md) | **同轴镜像比入对账面**:负向/正向比超出 β 带内推导区间即越带;单边动负向三行今后必须带正向镜像行联合检算(2026-10-05 判式换代批新增——d70f6c3/数值修复批两先例的单边动教训成文) |
| 直伤雷三行(6/8/8) ↔ 正向同轴行(heal_minor/pierce_strike/magic_bolt) | [地图战斗资源](../战斗循环/地图战斗资源.md) | **同轴镜像比入对账面**:负向/正向比超出 β 带内推导区间即越带;单边动负向三行今后必须带正向镜像行联合检算(2026-10-05 判式换代批新增——数值修复批两先例的单边动教训成文) |
| clear_blast 区间(24/40/32) ↔ p_perfect | [地图战斗资源](../战斗循环/地图战斗资源.md) | 完美封印第二份的补贴基数进封印激励对账;单边动清图奖励 = 变相动封印(2026-10-05 判式换代批新增) |
| 标中强化池权重 ↔ 正向池权重(77 分母) ↔ 关级恒等式奖励占比 [15,40%] | [地图战斗资源](../战斗循环/地图战斗资源.md) | 形态 (b) 专属池重摇启用时生效(现行不启):双池分布变动 = E_dig 与 W 同时漂移,须联合检算 + 恒等式占比复核(2026-10-05 判式换代批新增,条件生效行) |
五条经济契约的对账由[战斗结算](../战斗循环/战斗结算.md)闭合验收;挖掘风险不等式已于 2026-10-05 判式换代批重立(三层口径+β 带+条件二封印溢价——结构层接替与数值层带判读详见行动经济 F1 条款3 推导节),本系统的对账义务转为按 β 带逐 u 检算(常设重跑入口 `tools/clear_dominance_audit.py`,现行 u=3 β=1.17 微破下限登记态);清图支配性已于 2026-09-19 按现行值链重跑、裁决②"非支配设计态"维持(回执见 git 提交 2ca3e20,报告已退役)——判式换代后的全套判值重跑随本系统下一次调参批顺带执行。完美封印乘数的调参权在本系统。
五条经济契约的对账由[战斗结算](../战斗循环/战斗结算.md)闭合验收;挖掘风险不等式已于 2026-10-05 判式换代批重立(三层口径+β 带+条件二封印溢价——结构层接替与数值层带判读详见行动经济 F1 条款3 推导节),本系统的对账义务转为按 β 带逐 u 检算(清图支配性审计为常设重跑项,现行 u=3 β=1.17 微破下限登记态);清图支配性已于 2026-09-19 按现行值链重跑、裁决②"非支配设计态"维持——判式换代后的全套判值重跑随本系统下一次调参批顺带执行。完美封印乘数的调参权在本系统。
元进度接口预留:**结论——元进度解锁对难度的影响,以生存链区间的平移换算率接入;接口启用形态已定稿(2026-09-18 元进度定稿批,用户拍板):永久强化影响折算为通关率带平移量,判读口径不变;换算率表首版三行(生命上限 / 基础攻击 / 护甲等级——护甲行强条目,验收读数须按解锁后参数重测)与键结构见 `data/schemas/meta_table.gd`,数值行读 `data/tables/`、标定随元进度设计批。** 元进度数值行落地前,本系统不为它预留任何数值行。
元进度接口预留:**结论——元进度解锁对难度的影响,以生存链区间的平移换算率接入;接口启用形态已定稿(2026-09-18 元进度定稿批,用户拍板):永久强化影响折算为通关率带平移量,判读口径不变;换算率表首版三行(生命上限 / 基础攻击 / 护甲等级——护甲行强条目,验收读数须按解锁后参数重测)与键结构见元进度表字段定义,数值行读 `data/tables/`、标定随元进度设计批。** 元进度数值行落地前,本系统不为它预留任何数值行。
## 依赖与输出
@@ -260,7 +260,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P1 | 加难杠杆扫描落带 | 候选杠杆已登记(血量锚走廊、群战上探、注入衰减上调候选),域内取样扫描已执行(迭代 5——r_boss 0.8 档,见「Boss 设计」节加难走廊行),全杠杆落带扫描未完成;护甲等级换装点锚定(基准护甲等级自带敌方输出期望的结构性削减 + 减伤墙退役 + 伤害下限)的方向性偏移并入本扫描一并调带(带宽补扫含暴击档与折扣档);落带前现行行值维持 provisional-pending-scan。第二关行集随分支节点图批落地后并入本扫描(L2 读数基线/加成档已顺带实测,见 v0.7) |
| P1 | 挖掘风险不等式与清图支配性对账重跑 | **挖掘风险不等式半边已完成(2026-10-05 判式换代批,迭代14②号卡)**——判式三层重立写入文档+条件二封印溢价机制闭合+β 带判读制度化(残留=u=3 β=1.17 微破下限登记态,不强制回滚;δ 敏感度判读顺带出口报告),本行对挖掘风险不等式的「重跑义务」改判读回执:后续调参按 β 带检算执行,不再立独立重跑批。清图支配性已于 2026-09-19 按现行值链重跑(裁决②"非支配设计态"维持——缺口收窄主读数 L1≈24/L2≈45 当量〔u=1〕,设计权威回执=git 提交 2ca3e20,重跑入口 tools/clear_dominance_audit.py);调参批执行时按当期行值顺带重跑 |
| P1 | 挖掘风险不等式与清图支配性对账重跑 | **挖掘风险不等式半边已完成(2026-10-05 判式换代批,迭代14②号卡)**——判式三层重立写入文档+条件二封印溢价机制闭合+β 带判读制度化(残留=u=3 β=1.17 微破下限登记态,不强制回滚;δ 敏感度判读顺带出口报告),本行对挖掘风险不等式的「重跑义务」改判读回执:后续调参按 β 带检算执行,不再立独立重跑批。清图支配性已于 2026-09-19 按现行值链重跑(裁决②"非支配设计态"维持——缺口收窄主读数 L1≈24/L2≈45 当量〔u=1〕,重跑入口=清图支配性审计(常设重跑项));调参批执行时按当期行值顺带重跑 |
| P1 | 真人冒险成功率修正系数标定 | 带按模拟口径定义;试玩批次实测真人显著强于模拟者,系数待后续试玩批次定量 |
| P1 | 迭代 10 奖励重构带重扫义务 | 恢复经济收紧(关卡完成回满血→部分回血 20%,难度上压方向)+ 局内成长节奏变动(三选一件值/权重/次数上限 knob 全读冒险表;AP 件 = 每场战斗开始首轮 +1 AP 的强度敏感轴)——关卡完成率带重扫随曲线调参批现行窗执行;Boss 钥石掉落重锚(首杀 3/复杀 2)连带元进度产出标定义务重开(归元进度设计批) |
| P1 | 完美策略档的结构性加难杠杆 | 数值杠杆对完美档已被实测证伪,结构候选未裁决(登记 [待办清单](../../../../待办清单.md)) |
@@ -270,7 +270,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
## 调参表
本系统是全项目行值最多的系统。数值纪律(2026-09-17 降级批定稿):**正文与调参表只定规则、取值域与口径,具体行值一律读配置表(切片 `game/data/tables/` 的 enemy / skills / hero / board_gen / reward / settlement / status_effects 等),已锁数值以 `data/tables/` 对应表为准,本文不登记具体数值——防双写失同步**(切片 `.tres` 数值压缩批后文档行值滞后,双写失同步已被实证)。
本系统是全项目行值最多的系统。数值纪律(2026-09-17 降级批定稿):**正文与调参表只定规则、取值域与口径,具体行值一律读配置表(切片 `data/tables/` 的 enemy / skills / hero / board_gen / reward / settlement / status_effects 等),已锁数值以 `data/tables/` 对应表为准,本文不登记具体数值——防双写失同步**(切片 `.tres` 数值压缩批后文档行值滞后,双写失同步已被实证)。
首发行值几乎全部 provisional,其中加难相关数值处于 provisional-pending-scan(扫描未执行);完整行集读表,不在此整表搬运。
@@ -296,7 +296,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
| 技能冷却(拍) | 逐技能行读表;普攻冷却恒 0 不可配(空池不可能的结构前提——域校验锁死);撕咬 = 与突刺同倍率错开冷却轴的高频小爆发轮廓;毒爆 = 蛛化卓尔精灵专属池独占技 |
| 保底 M | 全局单旋钮,读技能表 pity_threshold;连续 M 拍执行普攻 → 下拍必出特殊技;单特殊技低权重池受保底支配 |
| 触发条件 | 情境技与闩型解锁的 fact 阈值读技能表 trigger 行值(2026-10-26 触发条件批设计闭合——词表与机制归敌人系统「触发条件」节);情境技的池内份额照走池权重行。**已投放两示范(2026-10-27 迭代 39,provisional)**:地精池孤注守势(self_hp_pct lte 35,纯增益技)+ L2/L3 群行位趁隙偷袭(last_round_end eq 停手,伤害技)——fact 阈值、权重、倍率与冷却逐行读表,本表零复制;地精池投放稀释含闸长期速率,EV 抽样锚已随重锚批换代(带宽注记随 `enemy.tres` 表注) |
| 池 EV 锚 | 静态份额与含闸长期速率实测读数登记于调参日志(EV 重锚与权重投值随本表修订)。**2026-09-19 节奏体验验证批登记**(60 distinct seeds × 双策略 × 两关,tools/skill_rhythm_audit.py——20 种子以下批次有种子偏置假象,读数须 ≥50 种子):特殊技执行份额 对表成立——地精 10.4%(静态 10%)/ 地精一伙 8.4% / 熊地精系 28.9–42.4% / 巨蜘蛛 34.8% / 酋长 28.9% / 卓尔 35.4%;**L2 池含闸速率抽样复核完成**(上项即含闸读数);保底越界 0/247(间隔 4 = M=4 合法上界);过涝信号 8/1440 场(最长连击 3);过旱按用户判读带集中在短命杂鱼(生存时长问题非池配置——见待办清单清单 Alpha 条目移交注记) |
| 池 EV 锚 | 静态份额与含闸长期速率实测读数登记于调参日志(EV 重锚与权重投值随本表修订)。**2026-09-19 节奏体验验证批登记**(60 组独立种子 × 双策略 × 两关的节奏实测,种子数下限 50 判读;20 种子以下批次有种子偏置假象):特殊技执行份额 对表成立——地精 10.4%(静态 10%)/ 地精一伙 8.4% / 熊地精系 28.9–42.4% / 巨蜘蛛 34.8% / 酋长 28.9% / 卓尔 35.4%;**L2 池含闸速率抽样复核完成**(上项即含闸读数);保底越界 0/247(间隔 4 = M=4 合法上界);过涝信号 8/1440 场(最长连击 3);过旱按用户判读带集中在短命杂鱼(生存时长问题非池配置——见待办清单清单 Alpha 条目移交注记) |
主角表与棋盘参数表(行值分别读 `hero.tres` 与 `board_gen.tres`,provisional):
@@ -325,7 +325,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
- 零红档:全部行集经区间扫描,不存在"最好情况也打不过"的战斗组合。
- 教学位绿档:第一关第一战与各新敌种首遇位的区间判定为绿档。
- 形态带成立:第一关形态 = 教学位绿 + 中段黄 + Boss 黄不红。
- 通关率带:**2026-10-03 带换代(用户拍板四带甲案)**——带 A 白板完美执行战役 [0,10%] / 带 B 白板 L1 [50,85%] / 带 C 满成长 [30,60%] 预立 / 带 D 响应曲线 ≤15pp·S 形;旧普通 L1 [35,55] / L2 [10,15]、完美 [35,65] 正式作废。测量口径维持冒险条件通关率(balance_verifier 冒险段派生);四带定稿全文见「期望值带」节「带换代」条。
- 通关率带:**2026-10-03 带换代(用户拍板四带甲案)**——带 A 白板完美执行战役 [0,10%] / 带 B 白板 L1 [50,85%] / 带 C 满成长 [30,60%] 预立 / 带 D 响应曲线 ≤15pp·S 形;旧普通 L1 [35,55] / L2 [10,15]、完美 [35,65] 正式作废。测量口径维持冒险条件通关率(由数值验证工具冒险段派生);四带定稿全文见「期望值带」节「带换代」条。
- 关卡完成余量带:关卡完成者剩余生命 p90 / p10 按现生命轴上限百分比表达,界值重标随验收批。
- 恒等式闭合:关级恒等式逐项对账闭合,残差 <5%(provisional 阈值);奖励输出占比 ∈ [15%, 40%]。
- Boss 锚在案:每个 Boss 行的血量锚比率记录在案且落在现行带内;越带须登记,不静默变更。
@@ -1,11 +1,11 @@
# 地图战斗资源
> 一句话:格面资源的产出、极性字典与清图奖励封装
> 状态:已定稿(自旧体系迁移)
> 状态:已定稿(自旧体系迁移);现行为 v1.2
## 设计目的
地图战斗资源是战斗域的资源层:每张雷图上每个格子携带什么资源、资源怎么摇定、正向与负向各兑付成什么效果、清图时整张板发什么,由它统一裁决。核心抽象是**格面资源(CellResource)**——关联到格子上的资源对象:装板时摇定类型身份,安全格与危险格一视同仁、同一类型池;资源本身不分好坏,被当成奖励还是惩罚,由玩家对这一格做的动作在结算期决定——开安全格、标中雷是正向,开危险格、标错安全格是负向(极性矩阵的定义归本文,路由执行归[战斗结算](战斗结算.md))。开局初始展开白送的那批格是**封锁格**——恒无资源,不再有任何加成(定义见[排雷玩法](../排雷循环/排雷玩法.md))。
地图战斗资源是战斗域的资源层:每张雷图上每个格子携带什么资源、资源怎么摇定、正向与负向各兑付成什么效果、清图时整张板发什么,由它统一裁决。核心抽象是**格面资源**——关联到格子上的资源对象:装板时摇定类型身份,安全格与危险格一视同仁、同一类型池;资源本身不分好坏,被当成奖励还是惩罚,由玩家对这一格做的动作在结算期决定——开安全格、标中雷是正向,开危险格、标错安全格是负向(极性矩阵的定义归本文,路由执行归[战斗结算](战斗结算.md))。开局初始展开白送的那批格是**封锁格**——恒无资源,不再有任何加成(定义见[排雷玩法](../排雷循环/排雷玩法.md))。
它守住三件事:每个非封锁格都有资源可兑(结构保证,不靠逐次掷运气);类型身份在装板期一次摇定、全程只读(在"数字恒真"的推理之外,类型是第二层规划素材);每个兑付都能在结算明细里指回挖开的哪一格。缺了这一层,挖格没有收益语言,"扫雷攒因、战斗结果"的兑现就断在最后一环。
@@ -70,7 +70,7 @@
### 候选集合:安全格与危险格的资源池
格面资源在每非封锁格上摇定身份时不区分格型——`_rollable_rows()` 把 cell 池正向行全集作为权重表,对所有非封锁格一视同仁地 `weighted_pick`。安全格与危险格候选集相同,权重分布相同,类型频次合计 = reward.tres cell 池正向行 weight 之和(现行值读表):
格面资源在每非封锁格上摇定身份时不区分格型——候选集合 = 格面资源池中全部正极性、权重大于零的类型行,不分格型一律参与摇号;负向兑付归战斗结算按极性路由。安全格与危险格候选集相同,权重分布相同,类型频次合计 = reward.tres cell 池正向行 weight 之和(现行值读表):
| 格型 | 候选集合(cell 池正向行,weight>0) | 类型频次合计 |
|---|---|---|
@@ -79,11 +79,6 @@
清图槽(clear_default 池,clear_blast)独立于格面资源——作用域为整张板,不挂任何一格。
引用(代码事实源):
- `game/core/reward_economy.gd#roll_board`:`_rollable_rows()` 第 207-213 行收 `pool_tag=cell & polarity=positive & weight>0` 全集;循环第 65-67 行对 `eligible` 清单(全部非封锁格)一视同仁。
- `game/core/minesweeper_rules.gd#_do_dig` 第 256-264 行:chain/dig 通道 `cell_revealed` 事件 `resource=true` 字段对安全格与危险格一致——注释明确"安全格与危险格一视同仁(危险格亦携带,负向兑付归 战斗结算 极性路由)"。
- `game/data/tables/reward.tres`:9 行正向行 weight 现行值读 `data/tables/reward.tres`。
候选集合具体行值(power / power_min / power_max / stacks / effect_id)随行更新——以 `data/tables/reward.tres` 为现行值源;GDD 表格只锁行集与权重分布语义。负向 9 行(enrage / off_guard / heal_backlash / enemy_alert / ap_drain / sapped / pierce_snare / magic_recoil / gp_loss——2026-09-20 八型批 ambush_double→enemy_alert 换装 + ap_drain 新增、同日 GP+商店房批 +gp_loss)weight=0 不参与摇号,按(类型, 极性)查表取用,不在候选集合内。
举证:本节为候选集合枚举,无计算。
@@ -101,7 +96,7 @@
- **档由两个参数定义:概率位 + 保底上限**。「永不保底」不单立规则——T4 的保底上限 = 0 即其统一表达;T1/T2 上限同为 0。
- **保底数量挂在型上、不挂在档上**:每个型各自登记「每板至少出几个」。档内登记保底的型数 ≤ 档上限 → 全部兑现;> 上限 → 每板从登记型中随机选定「上限」个兑现,其余型该板按普通概率出现、不保底。
- **补签纪律**:执行序 = 装板摇号(含偏运硬币/board_skew 投影)→ 按上限定本期保底型集合 → 校验补签 → 板上全程只读。补签只改类型身份:危险格与安全格不动(补签格可为雷可为安全格,危险格与安全格同池不破)、不消费权重(补签不是一次取号)、清图判定不动、不泄露元信息(补签格与摇号格不可区分)。粒度 = 板,换板重新校验。
- **补签纪律**:执行序 = 装板摇号(含偏运硬币投影)→ 按上限定本期保底型集合 → 校验补签 → 板上全程只读。补签只改类型身份:危险格与安全格不动(补签格可为雷可为安全格,危险格与安全格同池不破)、不消费权重(补签不是一次取号)、清图判定不动、不泄露元信息(补签格与摇号格不可区分)。粒度 = 板,换板重新校验。
- **GP 数值锚**:以战斗结束 GP 奖励为锚——小 GP 袋 ≈ 其 1/10、大宝箱 ≈ 其 1 倍(具体数值归调参批)。
- 大宝箱为枚举新增(九型→十型走本文修订流程,不随手加行);微光归通用增益组(五色相不变,组内不细分正好容下)。
- **对外稀有度映射**(现行事实源=[核心循环](../../01-核心循环.md)「档位与获取规则」节):T1=普通 / T2=优秀 / T3=稀有 / T4=传说;任何概率提升禁止改变本档保底承诺(保底=承诺非概率)。
@@ -128,13 +123,13 @@
| 类型 | 正向(奖励) | 负向(惩罚) |
|---|---|---|
| attack 增伤 | attack_up(hero_offense 池)——幅度/上限/权重见 `reward.tres` | enrage(enemy_output 池)——幅度/上限见 `status_effects.tres` enrage 行 |
| defense 护甲 | ac_up 盾牌加护(AC 掷点阈值轴——2026-09-16 换装,不入池)——幅度/上限/权重见 `reward.tres` | off_guard 失衡(AC 掷点阈值轴负向——2026-09-18 换装,不入池、退出 hero_defense 池)——幅度/上限见 `status_effects.tres` off_guard 行 |
| attack 增伤 | attack_up(主角输出池)——幅度/上限/权重见 `reward.tres` | enrage(敌方输出池)——幅度/上限见 `status_effects.tres` enrage 行 |
| 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` |
| blast 群伤 | spark_aoe 全体存活敌各吃一笔——威力/权重见 `reward.tres` | enemy_alert 警觉:敌方全体护甲等级 +1/层(`status_effects.tres` alert 行)——见 `reward.tres` |
| weaken 削弱 | weaken(enemy_output 池;同名重复授予只刷新剩余次数——2026-09-18 续时定案)——幅度见 `status_effects.tres` weaken 行 | sapped(hero_offense 池)——幅度见 `status_effects.tres` sapped 行 |
| weaken 削弱 | weaken(敌方输出池;同名重复授予只刷新剩余次数——2026-09-18 续时定案)——幅度见 `status_effects.tres` weaken 行 | sapped(主角输出池)——幅度见 `status_effects.tres` sapped 行 |
| action 行动点 | ap_up:下轮行动点 +1——见 `reward.tres` | ap_drain 迟滞:下轮行动点 −1——见 `reward.tres` |
| gp 财货 | gp_gain GP 袋:战斗内 GP 缓冲 +power,战斗完成随 gp_dropped 统一入钱包账本(兑付编排归[战斗结算](战斗结算.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` |
| magic 魔法 | magic_bolt 魔法矢:单目标克制直伤(attack_type=magic,同上)——威力/权重见 `reward.tres` | magic_recoil 魔法反噬:主角直伤,不经增益状态——威力见 `reward.tres` |
@@ -146,7 +141,7 @@
### 负向恒即时:惩罚没有库存语义
负向兑付的账户动作全部在授予段受理,不入库、不出窗口——惩罚不设库存、不留存量;其中**伤害形态(群伤/直伤类负向行)的扣血落地殿后到战斗段末**(2026-10-06 用户裁,单据暂存,见[战斗结算](战斗结算.md));主角在敌方还手中倒下则地图伤害账整体不兑(逐单留「未生效」行)。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的状态池;heal 正向当场结算;gp 正负两向(GP 袋 / 破财)当场执行——记入战斗内 GP 缓冲,战斗完成随 gp_dropped 与钱包结算(账本归[冒险结构](../冒险循环/冒险结构.md)),同样无库存语义。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。
负向兑付的账户动作全部在授予段受理,不入库、不出窗口——惩罚不设库存、不留存量;其中**伤害形态(群伤/直伤类负向行)的扣血落地殿后到战斗段末**(2026-10-06 用户裁,单据暂存,见[战斗结算](战斗结算.md));主角在敌方还手中倒下则地图伤害账整体不兑(逐单留「未生效」行)。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的状态池;heal 正向当场结算;gp 正负两向(GP 袋 / 破财)当场执行——记入战斗内 GP 缓冲,战斗完成随 GP 掉落与钱包结算(账本归[冒险结构](../冒险循环/冒险结构.md)),同样无库存语义。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。
举证:本节为兑付语义,无计算。
@@ -163,8 +158,29 @@
| 可配置可替换 | 内容 = 池行值;允许即时类(当场兑付,不入库) |
| 图类型差异化是预留插槽 | 现行只有默认槽;结构位已留,启用走棋盘生成词表落定 |
持有弃图回收专长时,同一清图奖励对象也可按旧图探索进度部分回收;这不改变正常清图与清图奖励的定义,例外口径见下节。
举证:本节为封装与流程规则,无计算。
### 弃图回收:按旧图探索进度兑付(设计定稿,待实现)
弃图回收让玩家不必挖完最后几个安全格,也能把已经推进的探索换成一部分清图奖励。它只读取被放弃的旧图,不把旧图判为清图,也不把新图判为已清图。
旧图退役前记录三项读数:装板初始已开安全格数、装板初始未开安全格数、退役时已开安全格数。探索进度定义为:
```
探索进度 =(退役时已开安全格数 − 装板初始已开安全格数)÷ 装板初始未开安全格数
```
进度限制在 0~1。玩家付费挖开与连锁展开的安全格都真实推进探索,计入分子;装板时白送的初始格、封锁格与危险格不计入。没有新探索就刷新,进度为 0,不能获得回收奖励。
回收兑付的是旧图在装板期已经摇定的清图奖励对象,按探索进度取对应份额;具体比例、档位、取整与数值归调参。它只发基础奖励的进度份额:
- 不触发完美封印加倍,不产生清图事件,不执行正常清图的整板兑付。
- 同一旧图只在成功退役时回收一次;新图不继承旧图探索进度。
- 刷新被拒绝时旧图不退役,也不回收;成功后的兑付时机归[战斗结算](战斗结算.md)本轮授予段。
- 正常清图仍发完整清图奖励对象;弃图回收不得让同等探索投入的收益无条件高于正常清图。
## 依赖与输出
本系统被两侧夹住:事实与数值从上游来,调用与呈现往下游去。下表就是删除任何一方时的影响面清单。
@@ -215,23 +231,23 @@
| 行 | 类型 | 极性 | 取值域 / 字段语义 | 现行值 | 所在池 |
|---|---|---|---|---|---|
| attack_up | attack | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → StatusEffectTable attack_up 引用) | 见 `data/tables/reward.tres` | 格面池 |
| ac_up | defense | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → StatusEffectTable ac_up 引用) | 同上 | 同上 |
| attack_up | attack | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → 状态效果表 attack_up 引用) | 见 `data/tables/reward.tres` | 格面池 |
| ac_up | defense | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → 状态效果表 ac_up 引用) | 同上 | 同上 |
| spark_aoe | blast | 正向 | power [0,999]·power_min/power_max 可选区间·weight [0,999](flat 行 effect_id 恒空) | 同上 | 同上 |
| heal_minor | heal | 正向 | power [0,999]·power_min/power_max 可选区间·weight [0,999](flat 行 effect_id 恒空) | 同上 | 同上 |
| weaken | weaken | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → StatusEffectTable weaken 引用) | 同上 | 同上 |
| enrage | attack | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → StatusEffectTable enrage 引用) | 同上 | 同上 |
| off_guard | defense | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → StatusEffectTable off_guard 引用) | 同上 | 同上 |
| weaken | weaken | 正向 | weight [0,999] · power 不消费(percent 行 effect_id → 状态效果表 weaken 引用) | 同上 | 同上 |
| enrage | attack | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → 状态效果表 enrage 引用) | 同上 | 同上 |
| off_guard | defense | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → 状态效果表 off_guard 引用) | 同上 | 同上 |
| heal_backlash | heal | 负向 | weight=0 不参与摇号·power [0,999](flat 行 effect_id 恒空) | 同上 | 同上 |
| enemy_alert | blast | 负向 | weight=0 不参与摇号·effect_id=alert(敌方护甲等级 +1——effect 引用行,unit=ac) | 同上 | 同上 |
| ap_up | action | 正向 | weight [0,999] 参与摇号 | 同上 | 同上 |
| ap_drain | action | 负向 | weight=0 不参与摇号 | 同上 | 同上 |
| gp_gain | gp | 正向 | weight [0,999] 参与摇号 · power [0,999]·power_min/power_max 可选区间(flat 行 effect_id 恒空) | 同上 | 同上 |
| gp_loss | gp | 负向 | weight=0 不参与摇号 · power [0,999](flat 行 effect_id 恒空) | 同上 | 同上 |
| sapped | weaken | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → StatusEffectTable sapped 引用) | 同上 | 同上 |
| sapped | weaken | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → 状态效果表 sapped 引用) | 同上 | 同上 |
| clear_blast | blast | 正向 | clear_default 池·power [0,999]·power_min/power_max 可选区间·weight [0,999] | 同上 | 清图槽 |
摇号只消费正向格面池的权重(类型频次由此涌现——具体权重读 `reward.tres`);负向行是纯兑付字典,按(类型,极性)查表取用,不消费权重。权重分布按四档概率结构组织(档成员与保底规则见「四档概率结构与保底」节;现行权重为基准锚,数值调整归调参批)。clear_blast 对全体存活敌各吃一笔当场兑付的 flat 伤害。RewardConfigTable 已随可选类删除下线——注入概率、按板保底等参数全部失义(旧「按板保底」指已删除的可选类道具注入保底,与四档结构的型级保底无关)。
摇号只消费正向格面池的权重(类型频次由此涌现——具体权重读 `reward.tres`);负向行是纯兑付字典,按(类型,极性)查表取用,不消费权重。权重分布按四档概率结构组织(档成员与保底规则见「四档概率结构与保底」节;现行权重为基准锚,数值调整归调参批)。clear_blast 对全体存活敌各吃一笔当场兑付的 flat 伤害。奖励配置表已随可选类删除下线——注入概率、按板保底等参数全部失义(旧「按板保底」指已删除的可选类道具注入保底,与四档结构的型级保底无关)。
结构冻结(改动走本文修订流程,不走调参):九型封闭枚举(伤害道具批 2026-09-18 修订五→七、格面资源八型批 2026-09-20 修订七→八、同日 GP+商店房批修订八→九)、危险格与安全格同池、极性矩阵、装板期预分配且无重摇、负向恒即时无库存、清图恒非空、除摇号外零随机(四档保底补签属装板期校验环节,消费摇号环节随机流,非重摇——补签后身份全程只读)。
@@ -244,7 +260,10 @@
- 类型封闭与字典完整:表里出现九型之外的类型、或任一型缺正向/负向行,加载拒绝。
- 未生效可见:满层增益、满血正向治疗、无存活敌的群伤,各自在结算明细记"奖励未生效"行,零折算、零补偿。
- 加载护栏:池空、清图池无正当量、增益引用悬空——任一命中即加载时返回错误并中止;默认内容加载通过。
- 表实一致:数据表字段与 `data/schemas/reward_table.gd` 逐字段一致。
- 弃图回收进度:付费挖开与连锁展开的安全格计入探索,初始格、封锁格与危险格不计;无新探索时进度为 0,不能获得回收奖励。
- 弃图回收恰一次:成功退役的旧图按当次探索进度回收其预摇清图奖励对象,新图不继承进度,同一旧图不能重复回收;被拒绝的刷新零兑付。
- 回收不是清图:弃图回收不触发完美封印加倍、清图事件或正常清图整板兑付;正常清图仍发完整清图奖励对象。
- 表实一致:数据表字段与奖励表字段定义逐字段一致。
## 待办
+10 -10
View File
@@ -1,7 +1,7 @@
# 增益状态
> 一句话:增益/减益的状态池、叠层与衰减
> 状态:已定稿(自旧体系迁移);现行为 v1.1
> 状态:已定稿(自旧体系迁移);现行为 v1.2
## 设计目的
@@ -168,11 +168,11 @@
| weaken 削弱 | 敌方攻击被削弱(同名重复标中只刷新剩余次数、层数恒 1) | 敌方 | 见 `status_effects.tres` weaken 行(取值域 [60,85],取负生效) | 时限读表 / 刷新(2026-09-18 续时定案) | 封印标中 |
| enrage 暴走 | 敌方攻击提升 | 敌方 | 见 `status_effects.tres` enrage 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(attack 型负向) |
| off_guard 失衡 | 主角护甲等级降低(AC 轴——掷点判定阈值下调、更易被命中;2026-09-18 换装,原百分比承伤池已退) | 主角 | 见 `status_effects.tres` off_guard 行 | 时限/上限读表 / 叠加 | 标空封印反噬 / 踩雷(defense 型负向)/ 敌方妨害技(2026-09-19 新效应词汇批——蛛网束缚载体,duration 由技能行覆盖) |
| sapped 乏力 | 主角输出降低(weaken 型负向,hero_offense 池) | 主角 | 见 `status_effects.tres` sapped 行 | 时限/上限读表 / 叠加 | 危险格负向 / 标空封印(2026-09-15 格面资源批)/ 敌方妨害技(大地脉动——L3 Boss 石君王独占,2026-09-20 第三关内容批) |
| empower 蓄力 | 敌方输出倍率增益(enemy_output 池——蓄力技载体;幅度读表) | 敌方 | 见 `status_effects.tres` empower 行 | duration 读表 = 1 个有效增益拍 / 刷新 | 敌方技能施放(2026-09-16 敌人技能池批) |
| fortify 硬化 | 敌方防御提升(**敌方防御池 enemy_defense**——硬化技载体;负值入池 = 主角攻击折半,2026-09-18 符号纠正——旧正值实际翻倍) | 敌方 | 见 `status_effects.tres` fortify 行 | duration 读表 = 1 个有效增益拍 / 刷新 | 敌方技能施放(2026-09-16 敌人技能池批) |
| sapped 乏力 | 主角输出降低(weaken 型负向,主角输出池) | 主角 | 见 `status_effects.tres` sapped 行 | 时限/上限读表 / 叠加 | 危险格负向 / 标空封印(2026-09-15 格面资源批)/ 敌方妨害技(大地脉动——L3 Boss 石君王独占,2026-09-20 第三关内容批) |
| empower 蓄力 | 敌方输出倍率增益(敌方输出池——蓄力技载体;幅度读表) | 敌方 | 见 `status_effects.tres` empower 行 | 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 语义的状态池落地)。敌方防御池(enemy_defense)是第四个乘区池:敌方作为防守方的百分比修正池,消费面 = [战斗结算](战斗结算.md)主角攻击的池乘区——四池对称(敌打主角 = hero_defense × enemy_output,主角打敌 = hero_offense × enemy_defense),状态池语义零延伸:仍是同构实例、side-wide 加算、不封顶不保底。
empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(技能行 buff_ref 引用,施放时长由技能行覆盖——技能冷却与增益持续是两个计时器):两行 duration 表值(读表)的口径是"先兑付后衰减含施放当轮"——施放发生在第 N 结算战斗段,存活到第 N+1 结算战斗段才兑付"下一拍",即"持续 1 个有效结算"(proposal 语义的状态池落地)。敌方防御池是第四个乘区池:敌方作为防守方的百分比修正池,消费面 = [战斗结算](战斗结算.md)主角攻击的池乘区——四池对称(敌打主角 = 主角防御池 × 敌方输出池,主角打敌 = 主角输出池 × 敌方防御池),状态池语义零延伸:仍是同构实例、side-wide 加算、不封顶不保底。
削弱行是当量锚在状态池侧的数据落点:封印标中的收益折算成统一价值后,必须严格高于最低档主角增益——"封印标中必须比随便挖一格更值",否则没人愿意封印。这条锚的契约归[行动经济](../排雷循环/行动经济.md),对照验收归[战斗结算](战斗结算.md);暴走与失衡两行分别是 attack/defense 型负向(极性矩阵"标错/踩雷"分支兑付)在状态池侧的数据落点:行内容与载体裁决归它,本表只承载行值(失衡已换装 AC 轴不入池——2026-09-18 结构修复批)。**失衡另有第二来源**:敌方妨害技(2026-09-19 新效应词汇批——蛛化卓尔精灵"蛛网束缚",敌方技能施放来源、duration 由技能行 buff_duration 覆盖)——状态池零新语义,同构实例照常受理;乏力 sapped = 妨害第二词汇,载体已落地(L3 Boss 灰矮人石君王独占妨害技「大地脉动」,2026-09-20 第三关内容批)。
@@ -226,16 +226,16 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
| alert 幅度 | [0, 4] | ac 行 magnitude | 警觉(敌方护甲等级 +1/层 provisional);消费 = 战斗结算护甲等级掷点判定 |
| weaken 幅度 | 百分比取值域 [60,85],取负生效 | percent 行 magnitude | 削弱;须过"封印标中锚"对照 |
| enrage 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 暴走;attack 型负向行,进敌方输出修正池 |
| off_guard 幅度 | 护甲等级 [−20,20] 且非 0 | ac 行 magnitude | 失衡;标空封印反噬载体,掷点判定阈值逐层下调(2026-09-18 换装 AC 轴,不入池、退出 hero_defense 池) |
| off_guard 幅度 | 护甲等级 [−20,20] 且非 0 | ac 行 magnitude | 失衡;标空封印反噬载体,掷点判定阈值逐层下调(2026-09-18 换装 AC 轴,不入池、退出主角防御池) |
| sapped 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 乏力;危险格负向 weaken 型,进主角输出修正池(2026-09-15 格面资源批补登记) |
| empower 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 蓄力;敌方技能池增益技载体,进敌方输出修正池 |
| fortify 幅度 | 百分比 [−100,100] 且非 0 | percent 行 magnitude | 硬化;敌方技能池增益技载体,进**敌方防御池 enemy_defense**(负值 = 减伤方向——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 维持——续时语义在 dur=1 下表现为重复拿重置当轮倒数;跨轮延续随将来 duration 拉长自然显形,数值归状态层重设计批)**;拉长 = 恢复跨轮延续、规划变简单 |
| 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,16] | max_stack | 仅叠加策略消费;刷新行层数恒 1——weaken / attack_up / ac_up 表值上限在续时语义下不消费(2026-09-18 / 2026-09-20),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 同口径)。这些联动值禁止独立调参,修订须重跑锚对照并同步 registry。
联动纪律:增伤与削弱是当量锚的两端;盾牌加护与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)的承伤预期联动(掷点折扣轴——AC 行值变更须复算生存链区间;2026-09-20 主角增益续时批后 ac_up 层数恒 1、局内可达 AC 上限 11,复算随调参批);削弱改续时后"单轮层数上限受[行动经济](../排雷循环/行动经济.md)行动点约束"的联动随之失效(2026-09-18 续时定案——同名重复标中只刷新剩余次数、层数恒 1;2026-09-20 起主角侧 attack_up / ac_up 同口径)。这些联动值禁止独立调参,修订须重跑锚对照并同步登记册。
## 验收标准
@@ -249,7 +249,7 @@ empower 与 fortify 是敌方技能池增益技在状态池侧的数据落点(
- 合成不设上下限:状态池输出的有效值恒等于层数 × 行值,越出百分比域不封顶不保底;地板在战斗结算侧。
- 快照抗重载:已授予实例的数值在战斗中不受配置表重载影响,只影响后续授予。
- 确定性回放:同一授予序列驱动两个独立状态池实例,生效清单(含排列顺序)与衰减轨迹逐项相同。
- 表实一致:配置表字段与 `data/schemas/status_effect_table.gd` 逐字段一致。
- 表实一致:配置表字段与状态表的字段定义逐字段一致。
## 待办
+42 -21
View File
@@ -1,7 +1,7 @@
# 战斗结算
> 一句话:轮末结算的编排与逐行兑现
> 状态:已定稿(自旧体系迁移);现行为 v1.1
> 状态:已定稿(自旧体系迁移);现行为 v1.3
## 设计目的
@@ -64,7 +64,7 @@
| ④ 衰减段 | 移除到期的临时效果;每轮恰好一次,含末轮——清图奖励先兑付、效果后衰减 |
| ⑤ 判定段 | 先判主角倒下(负),再判敌人清空(胜),都不满足则继续下一轮 |
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰七处(2026-09-16 敌人技能池批由两处扩为三处;2026-09-17 分支节点图批换板入编排后扩为五处;2026-09-20 地形变异首批类型定稿批扩为六处——变异布置;同日 GP+商店房批扩为七处——战斗结束掉落掷定):战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;技能摇号属于回合内随机,但时点前置到每轮开轮、同样发生在结算步骤流之外(消费专用 `behavior_roll` 封闭流,每敌按生成序派生独立子流,单敌出招轨迹可独立复放、互不穿插);护甲等级掷点属于回合内随机、发生在步骤流之内(消费专用 `armor_roll` 封闭流,逐次掷逐次取号,可逐位回放);奖励格摇号(消费 `reward_roll` 封闭流,装板期格面资源批派发)、变异布置(消费 `variant_place` 封闭流,装板与资源预分配之后——棋盘设置顺序第 4 步)与换板棋盘生成(消费 `board_gen` 封闭流,清图轮换板时点)均属初始化随机、发生在结算步骤流之外;战斗结束掉落掷定属出口随机(消费 `gp_drop` 封闭流,按战斗节点身份查商店表掷定 GP 掉落,与关级加值、战斗内缓冲合成本战 gp_dropped),发生在结算步骤流之外。七处皆封闭随机流,同种子同输入逐位复现。
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰七处(2026-09-16 敌人技能池批由两处扩为三处;2026-09-17 分支节点图批换板入编排后扩为五处;2026-09-20 地形变异首批类型定稿批扩为六处——变异布置;同日 GP+商店房批扩为七处——战斗结束掉落掷定):战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;技能摇号属于回合内随机,但时点前置到每轮开轮、同样发生在结算步骤流之外(消费专用 `behavior_roll` 封闭流,每敌按生成序派生独立子流,单敌出招轨迹可独立复放、互不穿插);护甲等级掷点属于回合内随机、发生在步骤流之内(消费专用 `armor_roll` 封闭流,逐次掷逐次取号,可逐位回放);奖励格摇号(消费 `reward_roll` 封闭流,装板期格面资源批派发)、变异布置(消费 `variant_place` 封闭流,装板与资源预分配之后——棋盘设置顺序第 4 步)与换板棋盘生成(消费 `board_gen` 封闭流,清图轮换板时点)均属初始化随机、发生在结算步骤流之外;战斗结束掉落掷定属出口随机(消费 `gp_drop` 封闭流,按战斗节点身份查商店表掷定 GP 掉落,与关级加值、战斗内缓冲合成本战 GP 掉落),发生在结算步骤流之外。七处皆封闭随机流,同种子同输入逐位复现。
举证:五段为流程编排,无计算;段序常量存配置表,加载时校验与定稿顺序完全一致,不一致拒绝启动。
@@ -79,7 +79,7 @@
| 治疗行 | 治疗生效一行:封顶后的真实回复量 |
| 奖励未生效行 | 满层拒收、满血治疗等合法例外一行 |
| 封印未中行 | 封印落空的零值归因行,每颗雷只记一次;标错的负向兑付另记效果行 |
| 蓄力行 | 敌人本拍蓄力不动手(攒下一击)时记一行 **(2026-09-16 敌人技能池批改型为施放行 `cast`:敌人本拍施放增益技——0 伤害动作、具名技能、不掷不折〔原"不兑付伏击"读怪奖励语义随 2026-09-20 八型批伏击退役作废〕;蓄力语义改写为纯攻击增益技,见[敌人系统](敌人系统.md))** |
| 蓄力行 | 敌人本拍蓄力不动手(攒下一击)时记一行 **(2026-09-16 敌人技能池批改型为施放行:敌人本拍施放增益技——0 伤害动作、具名技能、不掷不折〔原"不兑付伏击"读怪奖励语义随 2026-09-20 八型批伏击退役作废〕;蓄力语义改写为纯攻击增益技,见[敌人系统](敌人系统.md))** |
| 衰减行 | 到期临时效果移除一行 |
| 判定行 | 胜 / 负 / 继续各一行;终局判定行同时是胜负画面的数据锚 |
| 高光行 | 完美封印成立时一行,只记实际有效兑付 |
@@ -90,7 +90,7 @@
第一层按行型豁免两类:衰减行(到期移除本就是段级动作,不属于任何一格)、判定行(它是胜负出口的结论,不是棋盘事实)。这两类连来源类别都不必填。
第二层按来源类别豁免,封闭清单五类:主角攻击行(来源是主角单位本身)、敌方行动行(来源是当时动手的那个敌人单位)、清图奖励行(来源是"这张板被清掉"这一整板事实)、专长揭示掷格行(落空行没有真实棋盘格可指)、专长 GP 入账行(来源是钱包面而非棋盘面)——它们都不指向某个棋盘格,来源格校验因此跳过。
第二层按来源类别豁免,封闭清单六类:主角攻击行(来源是主角单位本身)、敌方行动行(来源是当时动手的那个敌人单位)、清图奖励行(来源是"这张板被清掉"这一整板事实)、专长揭示掷格行(落空行没有真实棋盘格可指)、专长 GP 入账行(来源是钱包面而非棋盘面)、专长刷新效果行(来源是刷新专长与被弃旧图,不指向单一格)——它们都不指向某个棋盘格,来源格校验因此跳过。
豁免不等于免检:按行型豁免的行仍要带齐公共字段、守住各自的语义约束(如衰减行的数值必须是本次移除的读数),按来源类别豁免的行仍必须写明来源类别,其余行必须指回一个真实存在的棋盘格。豁免只是免掉"来源格必须有效"这一条校验,不是免掉整份行协议。
@@ -100,7 +100,7 @@
基础 9 类写的是裸机制的账:玩家没有任何专长时,结算流水的每一行都出得了这九类。有了专长之后,效果引擎会在段内挂点插入额外的结算效果,这类效果写**扩展行**。扩展行是**一个**行型,不是每种专长效果各占一个行型——具体插的是哪种效果,由行携带的效果身份(effect_id 载荷)说明,呈现层按身份取各自的行文案。专长会不断增加、效果种类不可穷举,行型词表因此对扩展类封口:新增专长效果只新增载荷身份,不再新增行型。
扩展行与基础行共用同一套行协议:公共字段、来源归因、两层豁免、无来源即炸,全部照常生效。发射时点由段序骨架的挂点决定(时序与挂点施加面归架构文档「模块归属·战斗域装配现状」节),本节只管这行是什么、带什么字段。实现已收敛为单一扩展行型(`extended`)+效果身份载荷(`effect_id`);效果身份清单与发射点以实现侧 `battle_orchestrator.gd` 文件头注为准。
扩展行与基础行共用同一套行协议:公共字段、来源归因、两层豁免、无来源即炸,全部照常生效。发射时点由段序骨架的挂点决定(时序与挂点施加面归架构文档「战斗域装配现状」节),本节只管这行是什么、带什么字段。扩展行携带效果身份(effect_id 载荷);效果身份清单与各发射点归架构文档「战斗域装配现状」与「战斗编排域拆件定稿」节登记。
举证:本节为行协议约定,无计算。
@@ -123,7 +123,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
| 层数 × 行值 | 单条效果的叠层数与行值,读[增益状态](增益状态.md)的实例数据 | 行值归该系统 |
| 完美乘数 | 完美封印给清图奖励的乘数,配置值 | 读 `settlement.tres` p_perfect(现行值读表) |
**gp / ap 当量口径注记(2026-10-05 判式换代批 T3——口径空白登记)**:上式 flat 类枚举(直伤、治疗、行动点)不含 gp——现行对账按 **gp 当量 = flat power 代理**计(GP 袋 3 / 破财 2,方案报告假设 A5);ap 当量 = flat 表值 1,机会成本口径(+1AP ≈ 多挖一格 ≈ E_dig + 进度 + 信息价值)更高,差异仅登记不进主算(假设 A6)。gp 的精确口径应经商店定价换算(1 GP 的边际属性当量),归状态层/商店批复核后换代。封印溢价兑付(条款3 条件二形态 a,`settlement.tres` flag_hit_premium=125 provisional)按同型正向 ×(1+δ) 兑付——当量换算随同型行口径同步放大,整数量级取整四舍五入(与伤害管线 round_direction=floor 分域,registry 顺带登记)。
**gp / ap 当量口径注记(2026-10-05 判式换代批 T3——口径空白登记)**:上式 flat 类枚举(直伤、治疗、行动点)不含 gp——现行对账按 **gp 当量 = flat power 代理**计(GP 袋 3 / 破财 2,方案报告假设 A5);ap 当量 = flat 表值 1,机会成本口径(+1AP ≈ 多挖一格 ≈ E_dig + 进度 + 信息价值)更高,差异仅登记不进主算(假设 A6)。gp 的精确口径应经商店定价换算(1 GP 的边际属性当量),归状态层/商店批复核后换代。封印溢价兑付(条款3 条件二形态 a,`settlement.tres` flag_hit_premium=125 provisional)按同型正向 ×(1+δ) 兑付——当量换算随同型行口径同步放大,整数量级取整四舍五入(与伤害管线向下取整口径分域,登记册顺带登记)。
### 授予段兑付:极性路由,四类动作各归各系统资源
@@ -138,7 +138,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
| ① 插危险格、留到轮末判定 | 标中照常(该危险格资源正向) |
| ② 插危险格、再直接挖开 | 只受挖雷一层惩罚(该危险格资源负向照常兑付;挖雷惩罚因惊动触发);标中不发(封印已随挖掘消失、不入判定基线) |
| ③ 插安全格、留到轮末判定 | 标空惩罚照常(该安全格资源负向) |
| ④ 插安全格、再直接挖开 | 零惩罚零奖励——正向兑付作废,记「奖励作废(封印格直接挖开)」归因行(reward_ineffective 行型);标空判定同②自然缺席 |
| ④ 插安全格、再直接挖开 | 零惩罚零奖励——正向兑付作废,记「奖励作废(封印格直接挖开)」归因行;标空判定同②自然缺席 |
| 批 | 兑付 |
|---|---|
@@ -157,8 +157,8 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
| 即时治疗(flat) | heal 正向 | 主角按表值回复并封顶上限 | 满血时零兑付,记"奖励未生效"行 |
| 直伤(flat) | heal 负向 | 主角在战斗段末扣表值生命(暂存单殿后统一落地),不经增益状态 | 主角倒下则地图账整体不兑,逐单留"未生效"行 |
| 警觉加层 | blast 负向 | 敌方全体护甲等级 +1/层(读 `reward.tres` enemy_alert 行 → `status_effects.tres` alert 行),入状态池 | 直授无上限;战斗段由防守方护甲等级读数消费 |
| GP 缓冲(flat) | gp 正 / 负向 | GP 袋 / 破财按 power 记入战斗内 GP 缓冲(+power / −power);缓冲随战斗完成折进 battle_finished 的 gp_dropped 统一到账(GP 归[冒险结构](../冒险循环/冒险结构.md)) | 无拒收态——缓冲只在战斗边界与钱包结算,战斗域零钱包状态 |
| 行动点修正 | action 正/负向 | 下轮行动点 ±power(action_economy 排队,下一轮开始兑现后清零;下限 0) | 排队净值不跨轮保留;明细记效果行(effect_id=ap_delta) |
| GP 缓冲(flat) | gp 正 / 负向 | GP 袋 / 破财按 power 记入战斗内 GP 缓冲(+power / −power);缓冲随战斗完成折进战斗结束信号的 GP 掉落统一到账(GP 归[冒险结构](../冒险循环/冒险结构.md)) | 无拒收态——缓冲只在战斗边界与钱包结算,战斗域零钱包状态 |
| 行动点修正 | action 正/负向 | 下轮行动点 ±power(行动经济排队,下一轮开始兑现后清零;下限 0) | 排队净值不跨轮保留;明细记效果行(effect_id=ap_delta) |
警觉(blast 负向——2026-09-20 八型批伏击退役换装)为敌方 side-wide 状态:多个敌人共享同一状态值、互不消耗,只活一轮、轮末清零;战斗段由防守方护甲等级读数消费(我打敌 = 敌方 armor_class + Σ敌方侧 ac 型状态)。其余各型负向受池 clamp 与叠层上限承载。
@@ -216,7 +216,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
| 穿刺 | 被克 | 克制 | 中性 | 对魔法护甲克制;对轻甲被克;**重甲中性**(挡得住刀刃、不排斥穿刺——2026-09-18 用户拍板;现行无重甲敌种,纯结构位) |
| 魔法 | 中性 | 被克 | 克制 | 绕得过重甲,被魔法护甲吸收 |
修正三档:克制 +1 / 中性 0 / 被克 −1(加法点数,九格分布读 `settlement.tres` 甲效矩阵——2026-09-18 加法核换装:量纲由百分比改点数、五档词表退役;三档而非五档的理由 = 攻击基线个位数刻度下 ±2 触伤害下限或翻倍超重,假粒度;攻击成长后可扩弱档)。攻击类型是分量的固有属性(每个分量恒属普通 / 穿刺 / 魔法 之一),护甲类型是防守单位的固有属性,两者都不随层数、来源与轮次漂移;基础分量的类型归属是主角表的可配置值(营地永久强化的换型落点),矩阵键集 3×3 满格、结构冻结,数值 provisional。矩阵消费面 = 主角侧(奖励直伤道具 / 主角攻击基础分量)——敌方攻击现行不查矩阵,主角 armor_type 轴仍休眠、生效待敌方技能攻击类型批。主角增益与乏力经 hero_offense 池修正攻击合计(2026-09-18 结构修复批:普通分量直乘旁路退役;2026-09-19 充能退役后合计 = 单分量,见池乘区公式行)。
修正三档:克制 +1 / 中性 0 / 被克 −1(加法点数,九格分布读 `settlement.tres` 甲效矩阵——2026-09-18 加法核换装:量纲由百分比改点数、五档词表退役;三档而非五档的理由 = 攻击基线个位数刻度下 ±2 触伤害下限或翻倍超重,假粒度;攻击成长后可扩弱档)。攻击类型是分量的固有属性(每个分量恒属普通 / 穿刺 / 魔法 之一),护甲类型是防守单位的固有属性,两者都不随层数、来源与轮次漂移;基础分量的类型归属是主角表的可配置值(营地永久强化的换型落点),矩阵键集 3×3 满格、结构冻结,数值 provisional。矩阵消费面 = 主角侧(奖励直伤道具 / 主角攻击基础分量)——敌方攻击现行不查矩阵,主角 armor_type 轴仍休眠、生效待敌方技能攻击类型批。主角增益与乏力经主角输出池修正攻击合计(2026-09-18 结构修复批:普通分量直乘旁路退役;2026-09-19 充能退役后合计 = 单分量,见池乘区公式行)。
主角攻击 = 单一基础分量(2026-09-19 充能退役批):量 = 攻击力基线(表值),类型 = 主角表 `attack_type`(营地永久强化换型 = 改字段;未来冒险前参数面承接"按关配型"的策略位)。原"普通 + 穿刺/魔法充能三分量"模型整体退役——挖掘期与战斗期之间不再有弹药供给线,克制应对从局内攒桶改为冒险前配型 + 棋盘火力(穿刺弹/魔法矢等直伤奖励道具,消费面不变)。
@@ -228,9 +228,9 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
```
敌方一次技能攻击 = max(damage_floor, floor( 掷点档位技能基础分量 ) × 主角防御乘数 × 敌方输出乘数 ) + 必中附加分量 rider(若有——2026-09-19 新效应词汇批:max(damage_floor, floor(攻击基线 × rider_mult)),rider 为后置加法不进乘区不掷不折);增益/妨害技 = 0 伤害施放行(不掷不折),妨害状态按状态行 target_side 落主角
主角一次攻击 = max(damage_floor, floor( 攻击合计 × (100 + 主角输出乘数池合成值) ÷ 100 × (100 + 敌方防御乘数池合成值) ÷ 100 ) )——hero_offense(增伤/乏力载体)与 enemy_defense(硬化载体)两池串乘在攻击合计之后(2026-09-19 充能退役后合计 = 单分量)
主角一次攻击 = max(damage_floor, floor( 攻击合计 × (100 + 主角输出乘数池合成值) ÷ 100 × (100 + 敌方防御乘数池合成值) ÷ 100 ) )——主角输出池(增伤/乏力载体)与敌方防御池(硬化载体)两池串乘在攻击合计之后(2026-09-19 充能退役后合计 = 单分量)
粒度口径:增益池乘区作用在攻击合计层——合计为个位数时百分比档存在粒度取整(如合计 2 点 ×0.75 → 1,实际约 −33% 大于名义 −25%),为已知行为、接受现状;2026-09-18 数值语言拍板后百分比仅保留增益池 / AC 折扣档 / 暴击三处既有粗档,终值与量纲复核归状态层重设计批
乘数 = 1 + 池合成值 ÷ 100;池内加算、每池独立封顶 ±100 百分点 → 乘数域 [0, 2];四池对称:敌打主角 = hero_defense × enemy_output,主角打敌 = hero_offense × enemy_defense(2026-09-18 结构修复批:hero_offense 乘区接线落地——attack_up 普通分量直乘旁路退役,旧"切片现状"注记销项)
乘数 = 1 + 池合成值 ÷ 100;池内加算、每池独立封顶 ±100 百分点 → 乘数域 [0, 2];四池对称:敌打主角 = 主角防御池 × 敌方输出池,主角打敌 = 主角输出池 × 敌方防御池(2026-09-18 结构修复批:主角输出池乘区接线落地——attack_up 普通分量直乘旁路退役,旧"切片现状"注记销项)
护甲等级掷点:掷单骰(骰面读表),掷点 ≥ 护甲等级 + 攻击加成 → 全额;低掷按缺口查减伤档位表(边界与档值读表,无零档)
暴击:天然最大面(读表)→ 该次基础攻击分量 ×暴击倍率(读表)且恒全额(双方同规;掷点层概念,与技能重击严格区分)
伤害下限:任何来源的伤害落地 ≥ damage_floor(读表,全局 max(damage_floor, ·)——折扣与池归零都不产生 v=0 伤害行;2026-09-20 扩全来源,取值域 [1, +∞))
@@ -262,10 +262,28 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
└─ 关卡结束(上层显式调用)→ 清空秘宝背包,给出可见反馈
```
停手与耗尽收轮的回合恒为同图继续;换图指令只允许出现在清图轮,其他轮发换图属于程序错误、立即上抛。秘宝背包以关卡为界:战斗结束不清背包,跨战斗延续;关卡结束时一次性清空,清空前先返回快照计数,作为"剩余秘宝随关卡结束清空"的可见反馈。战斗道具背包已随可选类删除(2026-09-15)下线。(2026-10-09 注:清空对象 = 关卡域库存 treasure_inventory——秘宝过渡形态的机械惰性承载,其投放渠道已随迭代 11 第①批全退役、现行恒空;现行秘宝=营地同调装配件、整局冒险生效不经此域,拆线归实装批——见[秘宝](../跨局循环/秘宝.md)沿革节。)
停手与耗尽收轮的回合恒为同图继续;换图指令只允许出现在清图轮,其他轮发换图属于程序错误、立即上抛。秘宝背包以关卡为界:战斗结束不清背包,跨战斗延续;关卡结束时一次性清空,清空前先返回快照计数,作为"剩余秘宝随关卡结束清空"的可见反馈。战斗道具背包已随可选类删除(2026-09-15)下线。(2026-10-09 注:清空对象 = 关卡域秘宝库存——秘宝过渡形态的机械惰性承载,其投放渠道已随迭代 11 第①批全退役、现行恒空;现行秘宝=营地同调装配件、整局冒险生效不经此域,拆线归实装批——见[秘宝](../跨局循环/秘宝.md)沿革节。)
挖掘期刷新是玩家在挖掘中途主动换图,经[行动经济](../排雷循环/行动经济.md)扣费:扣费成功后按"旧板退役、生成新板、装板、资源预分配恰一次、变异布置"编排,本轮不打断——没有结算段、没有轮次结束。储备不足被拒是玩家决策的合法结果,不是编排错误。刷新图不发清图奖励,完美封印计数随装板重置。
### 换图系专长:刷新事实与本轮兑付(设计定稿,待实现)
持有换图系后续专长时,成功刷新除执行上述换图编排外,还产生一次 `board_refreshed` 事实。旧图退役前保存它的探索进度与清图奖励对象身份;新板就位后登记事实。刷新被拒绝时不产生事实,任何后续效果都不触发。同一轮多次成功刷新按发生顺序逐次登记,不合并成一次。
刷新只登记事实,不当场插入结算。各效果在既有五段内兑付:
| 专长 | 兑付规则 |
|---|---|
| 弃图轰击 | 每次刷新事实开一张全体敌人伤害单据,在本轮战斗段末经伤害控制器统一结账;目标按结账时的存活状态解析 |
| 换图蓄势 | 每次刷新事实当场累计一层,受表值上限约束;下一次主角攻击附加全部层数对应的伤害,随后清零;战斗结束清空 |
| 换图庇护 | 每次刷新事实在本轮授予段经既有护盾通道授予一次,不新开挖掘期状态入口 |
| 弃图休整 | 每次刷新事实在本轮授予段回复生命,按既有治疗规则封顶生命上限;满血时记未生效行 |
| 弃图回收 | 每次刷新事实在本轮授予段按旧图探索进度回收其清图奖励对象;进度与奖励口径归[地图战斗资源](地图战斗资源.md) |
弃图轰击若击杀最后敌人,仍由本轮判定段按既有顺序判胜,不新增挖掘期即时胜利出口。换图系效果使用扩展行留痕:来源写明专长与被弃旧图,弃图轰击走伤害单据,护盾、回血与回收各保留自己的兑付记录;它们不指向单一棋盘格,使用明细流水的专长刷新效果来源豁免,不伪造来源格。
弃图回收不是清图:不触发完美封印加倍,不发清图事件,不改变「刷新图不发清图奖励」。它兑的是被放弃旧图按探索进度回收的一部分奖励。
战斗开始按固定序列开张:关上配置热重载的闸门、初始化状态池、掷骰定敌方实际数量、生成敌人、装板并预分配资源恰一次、变异布置([地形变异](../排雷循环/地形变异.md)——棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置,消费 `variant_place` 封闭流)、发开轮通知并执行开轮摇号(摇定首轮各敌本拍动作)。上一关的大奖励(如血上限提升)经战斗开始入参的覆盖值通道生效,内容归[冒险结构](../冒险循环/冒险结构.md)。
举证:本节为编排流程,无计算。
@@ -294,13 +312,13 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
| 呈现面 | 形态 | 载荷与语义 |
|---|---|---|
| 呈现状态快照 `presentation_snapshot` | 只读函数,全量 | 轮次、战斗结果、相位、行动点与储备、刷新可用性、主角(生命/上限/攻击/存活)、敌人数组(顺位/显示名/生命/敌种/护甲类型/护甲等级/摇定动作〔pending_skill/type/name〕/敌方增益 effects/存活)、主角活跃效果投影——呈现层唯一的状态读取出口(敌人技能池批批 2 定稿:行为环当前拍/环进度字段随行为环退役拆除——2026-09-16,池与权重不公开) |
| 战斗进行中谓词 `is_battle_live` | 只读函数,布尔 | 战斗未出结果且主角在场——组合根高频相位判断的低成本面 |
| 奖励身份查询 `query_reward_identity` | 只读函数,单格 | 已开格奖励身份解析的代理,转[地图战斗资源](地图战斗资源.md)的身份查询(就绪相位守卫归对方)——组合根注入棋盘徽章回调 |
| 结算快照信号 `settlement_snapshot` | 信号,每次结算末 | 对象销毁前组装的全量状态快照(终局含胜负结果)——测试记录与数值分析消费 |
| 战斗结束信号 `battle_finished` | 信号,战斗结束时 | 载荷 = 胜负结果、主角余量生命与本战 GP 掉落 `gp_dropped`(掉落掷定 + 关级加值 + 战斗内缓冲的合计;销毁前捕获)——呈现层在核心对象销毁后读状态的时序防护,钱包入账归[冒险结构](../冒险循环/冒险结构.md) |
| 呈现状态快照 | 只读函数,全量 | 轮次、战斗结果、相位、行动点与储备、刷新可用性、主角(生命/上限/攻击/存活)、敌人数组(顺位/显示名/生命/敌种/护甲类型/护甲等级/摇定动作〔技能编号/类型/名〕/敌方增益/存活)、主角活跃效果投影——呈现层唯一的状态读取出口(敌人技能池批批 2 定稿:行为环当前拍/环进度字段随行为环退役拆除——2026-09-16,池与权重不公开) |
| 战斗进行中谓词 | 只读函数,布尔 | 战斗未出结果且主角在场——组合根高频相位判断的低成本面 |
| 奖励身份查询 | 只读函数,单格 | 已开格奖励身份解析的代理,转[地图战斗资源](地图战斗资源.md)的身份查询(就绪相位守卫归对方)——组合根注入棋盘徽章回调 |
| 结算快照信号 | 信号,每次结算末 | 对象销毁前组装的全量状态快照(终局含胜负结果)——测试记录与数值分析消费 |
| 战斗结束信号 | 信号,战斗结束时 | 载荷 = 胜负结果、主角余量生命与本战 GP 掉落(掉落掷定 + 关级加值 + 战斗内缓冲的合计;销毁前捕获)——呈现层在核心对象销毁后读状态的时序防护,钱包入账归[冒险结构](../冒险循环/冒险结构.md) |
另设一个诊断观测面(诊断快照 `diagnostic_snapshot`)供组合根诊断转储与行为探针消费:它暴露编排内部状态,不属于呈现契约,不作其他用途。
另设一个诊断观测面(诊断快照)供组合根诊断转储与行为探针消费:它暴露编排内部状态,不属于呈现契约,不作其他用途。
## P0 最小闭环
@@ -321,7 +339,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
## 调参表
结算用到的全部数值——当量、封顶、乘数、段序常量、甲效与护甲等级掷点参数——都存结算配置表,代码里零数值字面量;改数值就是改表回填。表值以快照进结算,战斗中途改表不生效。已锁字段与数值以 `data/schemas/settlement_table.gd` 与 `data/tables/settlement.tres` 为准。
结算用到的全部数值——当量、封顶、乘数、段序常量、甲效与护甲等级掷点参数——都存结算配置表,代码里零数值字面量;改数值就是改表回填。表值以快照进结算,战斗中途改表不生效。已锁字段与数值以结算表的字段定义与表行为准。
| 参数 | 域 | 现行值 | 说明 |
|---|---|---|---|
@@ -360,7 +378,10 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
- 无来源即炸:每行明细都能指回一个来源事实(棋盘格、单位、清图或段级结论);找不到来源即断言失败,行型与来源类别两层豁免清单见明细流水一节,豁免行仍受其它契约约束。
- 行型封闭:流水行型只出现本文登记的基础 9 类与扩展行;行型词表改动必须走本文修订并同步呈现层行格式化,未登记行型禁止落行。
- 伤害单据管线(2026-09-24):全部伤害(主角先手、敌方伤害技、棋盘克制直伤、群伤、反噬、地形变异)经伤害控制器单据管线兑现,兑付配方封闭五种;目标在结账时刻照规则解析;死亡事件由单位自行宣布——怪死其未结单据作废(零行、不进冷却不进保底计数)、主角死兑付终止;段界结账为现行口径、开单即结为回切结构位,两模式各自同种子同输入逐位复现。
- 条款3 对账(2026-10-05 判式换代批 T3——与行动经济 F1 条款3 重立同步):①聚合健康式 (1−ρ̂)·E_dig(u) − ρ̂·V_mine(u) ≥ 0 与 β 比值带 [1.2, 2.5] 判读逐 u 检算(常设重跑入口 `tools/clear_dominance_audit.py`,读数快照归 registry 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 缓冲)、标空与挖格兑付不乘溢价、危险格与安全格同池结构零触碰。
## 待办
@@ -1,7 +1,7 @@
# 战斗表现
> 一句话:战斗动画与演出(旧体系未单独成文)
> 状态:范围草案(待设计)
> 状态:范围草案(待设计);现行为 v1.2
## 设计目的
@@ -74,7 +74,7 @@
附着在同一次样张上的还有一项已登记的裁决(登记出处 = 敌人系统的呈现契约):敌人层绘制优先级的插入位置——白话就是敌人的姿态精灵画在绘制顺序的哪一层,会不会、以及如何与棋盘上的数字、封印印记、小人发生遮挡。它随美术测试一并裁决,不在本文提前定。
两项相邻裁定已有结论,设计时直接继承:**敌人呈现形态已裁决(2026-09-20 用户看对照样张拍板)——全站桩式(无框立绘+底座),主角同样站桩、状态小图标挂血条正下方,卡片式整体退役**(待办清单「敌人卡形态」解决移除,状态记录 99;battle.md 起草条款已随裁决改写,对照样张存档 `production/mockups/`);art-bible 的图标键契约已冻结,动画设计不必等图标终稿。
两项相邻裁定已有结论,设计时直接继承:**敌人呈现形态已裁决(2026-09-20 用户看对照样张拍板)——全站桩式(无框立绘+底座),主角同样站桩、状态小图标挂血条正下方,卡片式整体退役**(待办清单「敌人卡形态」解决移除,状态记录 99;battle.md 起草条款已随裁决改写,对照样张已在案存档);art-bible 的图标键契约已冻结,动画设计不必等图标终稿。
### 一屏样张的验收标准
@@ -92,6 +92,8 @@
判定与回退:第 1 项不过即演出元素回炉,禁止以牺牲可读性换氛围(改演出、不动数字口径);第 2–4 项不过对应资产或方案回炉重交,已过项结果保留。第 5 项(敌层插入位置)不产生过/不过,产出一条裁决记录:插入位置结论由[敌人系统](敌人系统.md)的呈现契约登记(~~敌人卡形态对照~~半边已于 2026-09-20 提前裁决完成——全站桩定稿,结论已回写 battle.md 起草条款)。样张通过 = 角色剪影、动画方案、战斗空间、敌层插入位置四件锁定物冻结;通过前不启动任何终稿资产与动画设计。
**现行状态(2026-10-30 战斗一屏样张批)**:五项验收的机检断言已随该批全绿(机器代理口径与实测读数查战斗一屏样张批 git 提交史,合并提交 3b66d1a6);样张整体过闸仍差方向稿目检先行(art-bible §1.4 闸形,目检项登记体验验证清单 ㊶)。
### 演出服从信息与可读性
两条现行硬约束,将来任何动画方案都禁止越过。其一,数字可读性高于氛围:挖掘期画面无杂音,动画禁止遮挡数字、禁止在挖掘期抢焦;结算期的色彩与能量走向以 art-bible 第 2 节为权威。其二,结算逐行兑现是信息载体:battle.md 已承诺不可整体跳过、只提供减动效选项(时长减半或直达终态,逐行兑现可加速不可省略),本文将来兑现其动画侧。
@@ -100,13 +102,13 @@
**定义**:轮末结算的呈现节奏规则——结算行按演出层排播队列分组依次呈现、轮切换以「第 N 轮」横幅驻留,把核心层一帧同步跑完的结算翻译成玩家可跟读的逐行过程;与结算演出覆盖层(光带横扫/跨区弹道,迭代 6)共同构成结算期的演出面。
**作用对象与范围**:只作用于演出层——排播常量在 `main.gd`(`PACE_SEGMENT_GAP` / `PACE_ROW_GAP`)与 `settlement_fx_view.gd`(`BANNER_DWELL`),横幅与节奏经 settlement_fx_view 呈现。核心层零触碰:`battle_orchestrator` 的「零 await」契约与「MC 与基线数据逐项比对」口径不受扰(T1 态 400/400 逐位实证在案)。
**作用对象与范围**:只作用于演出层——排播常量(段间间隔/段内间隔/横幅驻留)归演出层单点持有,本文登记现行值,横幅与节奏在演出层呈现。核心层零触碰:结算零界面等待、零动画等待,且「MC 与基线数据逐项比对」口径不受扰(T1 态 400/400 逐位实证在案)。
**生命周期**:进结算期起播——结算行按五段分组依序进明细带(段间 0.3s、段内 0.22s;段内值 0.06→0.22 系迭代 8 三轮修改批用户裁决「逐条演出可感知」);轮切换横幅驻留 0.6s 后消隐进下一轮;战斗结束排播收尾。**减动效双态**:开关开启 = 延迟归零同帧直落 + 横幅静态化(与设置面减动效联动——「可加速不可整体跳过」承诺的节奏侧兑现)。
**关联与边界**:结算顺序与结果归[战斗结算](战斗结算.md)(本节零触碰,逻辑先行、演出后行);减动效开关归[设置与本地化](../表现与支撑/设置与本地化.md);节奏常量以代码为事实源(上列三常量),本文登记现行值与沿革,表值化随演出批再议;fx_probe 轮横幅发射计数为验收断言面。
**关联与边界**:结算顺序与结果归[战斗结算](战斗结算.md)(本节零触碰,逻辑先行、演出后行);减动效开关归[设置与本地化](../表现与支撑/设置与本地化.md);节奏常量由演出层单点持有,本文登记现行值与沿革,表值化随演出批再议;横幅发射计数构成验收判据。
**状态**:已实装(2026-09-29 迭代 8 卡⑦已入库、三轮修改批调参追认;pacing_queue_test 第 15 套在册)。本节是本文第一块已落地内容,其余部分维持范围草案。
**状态**:已实装(2026-09-29 迭代 8 卡⑦已入库、三轮修改批调参追认;验收套件第 15 套在册)。本节是本文第一块已落地内容,其余部分维持范围草案。
## 依赖与输出
+11 -11
View File
@@ -1,7 +1,7 @@
# 敌人系统
> 一句话:敌人数据、技能池出招、意图呈现
> 状态:已定稿(自旧体系迁移);现行为 v1.3
> 状态:已定稿(自旧体系迁移);现行为 v1.4
## 设计目的
@@ -71,7 +71,7 @@
| 规则 | 含义 |
|---|---|
| 敌方效果对全体生效 | 范围效果作用于所有存活敌人——混编放开后(2026-10-07 行集多行批)场上可多种敌种共存,结算语义按存活单位逐一生效、不依赖"场上全同种"前提;"只打某一个"的单体寻址接口已在[增益状态](增益状态.md)〈单体寻址扩展位〉节定稿,随首个单体效果的内容批开放(见 P1) |
| 行集构成纪律 | 敌人表按(关卡序号 × 深度带)存放;深度带=节点深度的区间——逐局生成使单点深度不再固定,配池按带不按点,难度坡随带递增。同一带键行集允许多行、允许不同敌种混编;Boss 行按终点配置,多终点各配一行(走哪条路决定打谁)。纪律三条,违规加载期返回错误并止动:①行集内 enemy_id 唯一(同一敌种在同行集仍只许一行);②同带键多行必须表文件内相邻,行序 = 声明序 = 生成序(行序即契约);③行集实例总数(各行骰后数量求和)≤ 4,单行数量域 [1,3] 不变(UNIT_COUNT_MAX 语义 = 单种数)。行数不设上限;同关随深度换敌种合法 |
| 行集构成纪律 | 敌人表按(关卡序号 × 深度带)存放;深度带=节点深度的区间——逐局生成使单点深度不再固定,配池按带不按点,难度坡随带递增。同一带键行集允许多行、允许不同敌种混编;Boss 行按终点配置,多终点各配一行(走哪条路决定打谁)。纪律三条,违规加载期返回错误并止动:①行集内 enemy_id 唯一(同一敌种在同行集仍只许一行);②同带键多行必须表文件内相邻,行序 = 声明序 = 生成序(行序即契约);③行集实例总数(各行骰后数量求和)≤ 4,单行数量域 [1,3] 不变(单行数量上限语义 = 单种数)。行数不设上限;同关随深度换敌种合法 |
| 主角数据行 | 主角战斗数值单独放一张全局单行表:生命上限、基础攻击力、护甲等级三字段(见调参表) |
| 基础攻击恒存在 | 基础攻击不许配 0(配置为 0 加载期拒绝)——零基线乘任何百分比增益恒为零,"胜利路径恒有"的保障从数据层杜绝死锁 |
| 先手攻击不是敌方技能拍 | 每次结算战斗段主角先手攻击顺位最前的存活敌人,这是固定动作;奖励侧的道具与增益是基础攻击之外的规划面伤害,归[地图战斗资源](地图战斗资源.md) |
@@ -91,7 +91,7 @@
| 钩爪恐魔 | 普通攻击 + 撕咬 + 蓄力 | 行值读表 | 行值读表 | L2/L3 群行位(蜘蛛同构池) |
| 灰矮人石君王 | 普通攻击 + 重击 + 大地脉动 + **岩崩**(半血解锁) | 行值读表 | 行值读表 | L3 关底 Boss;大地脉动独占 |
护甲类型(轻甲 / 魔法护甲 / 重甲)与攻击类型的克制结算矩阵归[战斗结算](战斗结算.md),本系统只提供每个敌种的护甲类型数据——**护甲列自 2026-10-05 技能池执行批起改读表口径**(本表轮廓列不再记具体甲型:现行 L1 全轻甲 / L2 仅 Boss 魔法甲 / L3 heavy 登台的错峰投放以 `enemy.tres` 行值为准;历史「本表写魔甲/重甲」与表失同步的欠账顺带清偿)。护甲等级是另一条数值轴(防守方字段,基础攻击掷点的折扣判定读数):敌人表与主角表各增 `armor_class` 一列(域 [0, 999]),掷点与折扣的结算归[战斗结算](战斗结算.md),行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)——三敌种首关与主角的护甲等级行值读配置表(第一关怪物 AC 相对较低,正式行值待怪物设定投值,登记[待办清单](../../../../待办清单.md)),两条轴的概念与字段禁止混用。举证:生成与护甲为数据面规则,无计算。
护甲类型(轻甲 / 魔法护甲 / 重甲)与攻击类型的克制结算矩阵归[战斗结算](战斗结算.md),本系统只提供每个敌种的护甲类型数据——**护甲列自 2026-10-05 技能池执行批起改读表口径**(本表轮廓列不再记具体甲型:现行 L1 全轻甲 / L2 仅 Boss 魔法甲 / L3 重甲登台的错峰投放以 `enemy.tres` 行值为准;历史「本表写魔甲/重甲」与表失同步的欠账顺带清偿)。护甲等级是另一条数值轴(防守方字段,基础攻击掷点的折扣判定读数):敌人表与主角表各增 `armor_class` 一列(域 [0, 999]),掷点与折扣的结算归[战斗结算](战斗结算.md),行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)——三敌种首关与主角的护甲等级行值读配置表(第一关怪物 AC 相对较低,正式行值待怪物设定投值,登记[待办清单](../../../../待办清单.md)),两条轴的概念与字段禁止混用。举证:生成与护甲为数据面规则,无计算。
### 技能池:每敌种一张带权技能池,每拍开轮摇号出招
@@ -111,12 +111,12 @@
|---|---|---|
| skill_id | 全部 | 主键,池条目引用键 |
| display_name | 全部 | 玩家可见名(明细行与动画共用) |
| type | 全部 | 封闭枚举 {普通攻击, 伤害, 增益, 妨害}——**新效应词汇批 2026-09-19 由三扩四**(设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0;多段命中经用户拍板退役不入枚举);新类型走本文修订 |
| type | 全部 | 封闭枚举 {普通攻击, 伤害, 增益, 妨害}——**新效应词汇批 2026-09-19 由三扩四**(设计权威 proposal-status-vocabulary.md〔提案已吸收退役——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0;多段命中经用户拍板退役不入枚举);新类型走本文修订 |
| damage_mult | 仅伤害 | 伤害倍率;域 [1.1, 16] provisional。普通攻击恒 1、不可配 |
| rider_mult | 仅伤害、可省 | **必中附加分量(2026-09-19 新增)**:域 (0, 1],缺省无字段 = 无附加;分量值 = max(伤害下限, floor(攻击基线 × rider_mult))——随施放者攻击基线缩放,后置加法、必中全额、不进乘区、不出暴击(后置加法同族);价位锚 0.75 = AC 折扣档最轻档"不掉档的稳定伤害" |
| cooldown_beats | 全部 | 施放后连续 N 拍不可摇中;普通攻击恒 0、不可配 |
| buff_ref / buff_duration | 仅增益与妨害 | 效果引用(指向状态效果行)与持续回合数;**落点由状态行 target_side 路由**(增益 → 敌方侧 / 妨害 → 主角侧,施放类 side 守卫加载拒绝)——妨害技零新授予通道 |
| trigger(触发条件) | 全部,可省 | {fact, op, value, latch} 四键——fact 与 op 取「触发条件」节封闭词表,value 类型与域随 fact,出域加载期拒绝;缺省无字段=无条件技,旧行为零变化(2026-10-26 情境条件触发器批立列、2026-10-27 迭代 39 实装落地;原池条目字段 unlock_hp_pct 收编为本字段闩型首用例) |
| 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;多段命中「命中段数」保留以防复活) |
普通攻击也是一行技能行,两条域校验锁死它的特殊性:倍率恒 1、冷却恒 0——想配倍率或冷却的就不是普攻,是伤害技。技能伤害存倍率、不存固定值:伤害 = 施放者攻击基线 × 技能倍率——同一行"重击",不同敌种按各自攻击基线缩放 = 基线 A × 技能倍率 M(行值读 enemy.tres / skills.tres),深层强化行自动缩放,这是共享技能表成立的前提;**固定的绝对值伤害不满足该前提**(不随基线缩放、跨关稀释——Brotato 教训),"固定值伤害"的合法形态是**必中附加分量 rider_mult**(×攻击基线的必中小倍率,2026-09-19 实装)。增益技带效果引用与持续回合,百分比天然按施放者同池生效,无缩放问题;妨害技同构,落点由状态行 target_side 路由到主角。
@@ -179,7 +179,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
| 死亡作废 | 照常——摇定后兑现前死亡,本拍作废、不进冷却不进保底计数 |
| 意图呈现 | 三档规格不变——情境技属特殊技,走类型级预告+结算明细具名,因果由技能名承载(如"趁隙偷袭");闩型保留解锁弹出规格(见"目标顺位与意图线索"节) |
半血解锁的收编:原池条目字段 unlock_hp_pct(2026-10-05 技能池执行批)收编为本字段闩型首用例——Boss 解锁技=trigger {fact: self_hp_pct, op: lte, value: 行值, latch: 真};解锁弹出规格(归因明细行+一次性技能名 toast)、单向闩语义、双面过滤行为全部不变(字段换代与三 Boss 行值迁移已随 2026-10-27 迭代 39 实装落地)。
半血解锁的收编:原池条目的血量百分比字段(2026-10-05 技能池执行批)收编为本字段闩型首用例——Boss 解锁技=trigger {fact: self_hp_pct, op: lte, value: 行值, latch: 真};解锁弹出规格(归因明细行+一次性技能名 toast)、单向闩语义、双面过滤行为全部不变(字段换代与三 Boss 行值迁移已随 2026-10-27 迭代 39 实装落地)。
首批落地(已实装——2026-10-27 迭代 39):①三 Boss 解锁技迁移为闩型条件(行为零变化);②两个示范投放——地精池+低血防御增益「孤注守势」(self_hp_pct lte 35,纯增益)、L2/L3 群行位+停手偷袭伤害技「趁隙偷袭」(last_round_end eq 停手)。投放敌种、权重与阈值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md);"每池增益≤1"纪律不动,条件技照常计入预算。
@@ -203,7 +203,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
即:普攻完全不可预测;特殊技有一次类型级预告,不可撤销,但给"继续挖还是停手"的决策依据。
**半血解锁弹出规格(2026-10-05 技能池执行批,D8 用户裁;内容数据驱动扩展批 T1 补登记——解锁判定点=通用事件「血量阈值 `enemy_hp_threshold`」的发射点,对所有带闩型血量触发条件的敌人逐个发射、非 Boss 专属——Boss 语义留给订阅条件判定;条件载体 2026-10-26 触发条件批起改技能行 trigger 闩型〔原池条目字段 `unlock_hp_pct` 收编,行为零变化,见「触发条件」节〕)**:Boss 血量降至解锁阈值(trigger 行值,域 [1,99],缺省无字段=无门槛)时,下一个开轮摇号前出一次归因明细行「XX 解锁了 YY」(高光键 `skill_unlock`,行型复用 highlight 零新增);呈现层对该行弹**一次性技能名 toast**(轻量提示,非演出——零动画资产、不改演出规格),只报技能名不报数值,池与权重不公开纪律不变。解锁=单向闩(治疗抬血不解锁);锁定条目双面过滤(不进可摇集合与保底特殊技集合——保底禁止逼出未解锁技);解锁后冷却初值 0 即可摇。投放纪律=机制通用、闩型解锁用例仅 Boss 专属技投放(enemy 表无 boss 编码位,硬闸须跨表查节点类型——不做硬闸,本句即文档纪律登记)。旧环"下一拍全程亮出(意图徽标 + 环点)"的预告粒度随行为环退役。技能动画的攻/防系映射归[战斗表现](战斗表现.md),敌人侧摇定动画位归[HUD 框架](../表现与支撑/HUD框架.md)(呈现载体 = 站桩立绘,2026-09-20 全站桩拍板),本系统只锁上面这张三档规格。
**半血解锁弹出规格(2026-10-05 技能池执行批,D8 用户裁;内容数据驱动扩展批 T1 补登记——解锁判定点=通用事件「血量阈值 `enemy_hp_threshold`」的发射点,对所有带闩型血量触发条件的敌人逐个发射、非 Boss 专属——Boss 语义留给订阅条件判定;条件载体 2026-10-26 触发条件批起改技能行 trigger 闩型〔原池条目的血量百分比字段收编,行为零变化,见「触发条件」节〕)**:Boss 血量降至解锁阈值(trigger 行值,域 [1,99],缺省无字段=无门槛)时,下一个开轮摇号前出一次归因明细行「XX 解锁了 YY」(高光键为技能解锁,行型复用高光行零新增);呈现层对该行弹**一次性技能名 toast**(轻量提示,非演出——零动画资产、不改演出规格),只报技能名不报数值,池与权重不公开纪律不变。解锁=单向闩(治疗抬血不解锁);锁定条目双面过滤(不进可摇集合与保底特殊技集合——保底禁止逼出未解锁技);解锁后冷却初值 0 即可摇。投放纪律=机制通用、闩型解锁用例仅 Boss 专属技投放(enemy 表无 boss 编码位,硬闸须跨表查节点类型——不做硬闸,本句即文档纪律登记)。旧环"下一拍全程亮出(意图徽标 + 环点)"的预告粒度随行为环退役。技能动画的攻/防系映射归[战斗表现](战斗表现.md),敌人侧摇定动画位归[HUD 框架](../表现与支撑/HUD框架.md)(呈现载体 = 站桩立绘,2026-09-20 全站桩拍板),本系统只锁上面这张三档规格。
| 规格 | 含义 |
|---|---|
@@ -251,7 +251,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
| P1 | 情境条件触发器(低血触发防御、玩家停手时偷袭技等) | 已实装(2026-10-27 迭代 39——三 Boss 闩型迁移+两示范投放,见「触发条件」节与「首批落地」段) |
| P1 | 个体化变体池(A 型好斗 / B 型守势、精英专属池) | 共识点 A 的 A2 案;登记[待办清单](../../../../待办清单.md) |
| P1 | 全局特殊间隔旋钮 | 特殊技背靠背实测仍嫌时的备用闸;登记[待办清单](../../../../待办清单.md) |
| P1 | 新效应词汇批(妨害技 + 必中附加分量已实装 2026-09-19——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../../../待办清单.md)(已解决移除,去向见状态记录) |
| P1 | 新效应词汇批(妨害技 + 必中附加分量已实装 2026-09-19——设计权威 proposal-status-vocabulary.md〔提案已吸收退役——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../../../待办清单.md)(已解决移除,去向见状态记录) |
| P1 | 敌种名录扩容与投放节奏(含第二关池与投放) | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| P1 | 单体寻址("只打某一个敌人"的目标指定) | 归集语义与呈现已裁定(2026-10-26;语义归[增益状态](增益状态.md)〈单体寻址扩展位〉节);实现随首个单体效果的内容批落地 |
| P2 | 敌方技能图鉴 / 学习呈现(玩家侧技能知识系统) | 池不公开口径下的进阶信息面;登记[待办清单](../../../../待办清单.md) |
@@ -269,7 +269,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
| unit_count_max | [unit_count, 3],可省略 | 数量区间上界;开战时在区间内掷定本战实际数量;缺省 = 定值 |
| atk_min / atk_max | [0, 999] 可选配对 | 攻击基线区间:开战掷定一次、整战持久;缺省 = 定值行 |
| armor_type | {轻甲, 魔法护甲, 重甲},缺省轻甲 | 克制结算归[战斗结算](战斗结算.md) |
| attack_type | {普通, 穿刺, 魔法},缺省普通 | 敌方攻击类型轴(**2026-10-13 迭代 24 N3 开列实装**:20 行全填 normal,ATTACK_TYPES 闭集枚举+缺省回退向后兼容,与 armor_type 同位同款);消费面 = 战斗结算敌方技兑付路径的护甲克制矩阵查表(敌方开单第 9 参传值+克制消费点,见战斗结算);行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| attack_type | {普通, 穿刺, 魔法},缺省普通 | 敌方攻击类型轴(**2026-10-13 迭代 24 N3 开列实装**:20 行全填 normal,攻击类型闭集枚举+缺省回退向后兼容,与 armor_type 同位同款);消费面 = 战斗结算敌方技兑付路径的护甲克制矩阵查表(敌方开单第 9 参传值+克制消费点,见战斗结算);行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| armor_class(护甲等级) | int [0, 999],缺省 0 | 防守方护甲等级——基础攻击掷点的折扣判定读数,掷点结算归[战斗结算](战斗结算.md);与 armor_type(克制属性轴)两概念禁止混用;行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| hp | [1, 999] | 单位生命基线(难度修饰前) |
| atk | [0, 999] | 攻击基线 |
@@ -298,7 +298,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
各敌种行值、池权重与池 EV 读数(含闸长期速率实测)一律读配置表与调参日志,本文不登记具体数值;EV 重锚与权重投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)。
结构冻结(非调参项,修订走本文流程):技能类型枚举封闭、每池恰一普攻、每池增益 ≤1、死亡即不动手(摇定后兑现前死亡作废不进账)、每结算每存活敌恰行动一次、行序、先作用后推进、固定顺位、**行集构成纪律(2026-10-07 行集多行批换代——原「每坐标恰一敌种」退役:行集内 enemy_id 唯一 + 同带键多行文件内相邻且行序 = 声明序 = 生成序 + 行集实例总数 ≤ 4)**、零随机(本系统自身语义零随机不变——摇号与掷点随机一律在[战斗结算](战斗结算.md)结算换算层按封闭随机流掷定,可回放契约 = 同种子同输入逐位复现,单敌出招轨迹可独立复放)。联动纪律:技能倍率 × 攻击(伤害拍支配)、权重 × 冷却 × 保底(池节奏三联动)禁止独立调参,联调须重跑方向性预检并同步 registry(登记 [待办清单](../../../../待办清单.md))。
结构冻结(非调参项,修订走本文流程):技能类型枚举封闭、每池恰一普攻、每池增益 ≤1、死亡即不动手(摇定后兑现前死亡作废不进账)、每结算每存活敌恰行动一次、行序、先作用后推进、固定顺位、**行集构成纪律(2026-10-07 行集多行批换代——原「每坐标恰一敌种」退役:行集内 enemy_id 唯一 + 同带键多行文件内相邻且行序 = 声明序 = 生成序 + 行集实例总数 ≤ 4)**、零随机(本系统自身语义零随机不变——摇号与掷点随机一律在[战斗结算](战斗结算.md)结算换算层按封闭随机流掷定,可回放契约 = 同种子同输入逐位复现,单敌出招轨迹可独立复放)。联动纪律:技能倍率 × 攻击(伤害拍支配)、权重 × 冷却 × 保底(池节奏三联动)禁止独立调参,联调须重跑方向性预检并同步登记册(登记 [待办清单](../../../../待办清单.md))。
## 验收标准
@@ -314,7 +314,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
- 快照抗改表:实例化后修改敌人表,已生成单位的数值投影与实例池逐字不变;新实例才用新表值。
- 表校验:越域值、池权重 ≤ 0、空池、普攻配倍率/冷却、池增益 > 1、skill_id 悬空、行集构成违例(行集内同敌种第二行 / 同带键多行不相邻)均在加载期返回错误并止动;域内合法极端(攻击基线 0)不拒;主角表恰一行、攻击配 0 拒绝。
- 零随机回放:同一动作与受击序列驱动两个独立实例,存活投影、逐实例摇定轨迹、顺位返回序列逐项相同。
- 表实一致:三张配置表字段与 `data/schemas/enemy_table.gd`/`hero_table.gd`/`skill_table.gd` 逐字段一致。
- 表实一致:敌人表、主角表、技能表三张配置表的字段与各自字段定义逐字段一致。
## 待办
@@ -1,7 +1,7 @@
# 地形变异
> 一句话:格子的非雾变异(荆棘丛/藤蔓缠绕/岩浆/烬覆/落石/矿尘)与 Boss 图整图变异(贫矿脉),挖掘中可遭遇的地貌偏离
> 状态:已定稿(首批类型随 2026-09-19 定稿批落地,第三关一对随 2026-09-20 第三关内容批落地;首个整图变异随 2026-10-04 全局作用域变异首发批落地)
> 状态:已定稿(首批类型随 2026-09-19 定稿批落地,第三关一对随 2026-09-20 第三关内容批落地;首个整图变异随 2026-10-04 全局作用域变异首发批落地);现行为 v1.1
## 设计目的
@@ -69,11 +69,11 @@
- 直伤量为加法核小整数(effect_value ∈ [1,9],行值 provisional 归难度曲线与Boss);每格携带单类型单 id。
- 变异格未开时无预告呈现(遭遇式;视觉标识归表现层升级批)。
候选池门控(框架级现行机制):一种变异要进入本关的布置候选,须同时满足三个条件——它的解锁关号不大于当前关卡序号(直接整数比较)、它声明的地形(board_types)与本地形有交集(声明为空集等于通配)、且节点身份匹配(scope=global 行仅 Boss/精英节点入池,普通战斗节点静默跳过——2026-10-04 全局作用域变异批增第三维;elite 系前向词位,首发零表行)。候选池为空是合法状态:静默布置一张无变异的普通图,不返回错误、不警告。门控谓词与取值域见本文候选池门控节与 `data/schemas/variation_table.gd`。
候选池门控(框架级现行机制):一种变异要进入本关的布置候选,须同时满足三个条件——它的解锁关号不大于当前关卡序号(直接整数比较)、它声明的地形(board_types)与本地形有交集(声明为空集等于通配)、且节点身份匹配(scope=global 行仅 Boss/精英节点入池,普通战斗节点静默跳过——2026-10-04 全局作用域变异批增第三维;elite 系前向词位,首发零表行)。候选池为空是合法状态:静默布置一张无变异的普通图,不返回错误、不警告。门控谓词与取值域见本文候选池门控节与变异表字段定义。
### 全局作用域型(scope=global——Boss 图整图变异)
region 型变异是「格子级」的:布置段往候选格域写格子快照。global 型变异是「整图」的:整图一个类型生效,不落任何格子(棋盘持有整图 `board_variants` 字段,逐格 `variants` 恒空)。**每图一型的整图推广(2026-10-04 用户裁决)**:普通图带 region 片,Boss/精英图带 global 型——两类图各自至多一型。
region 型变异是「格子级」的:布置段往候选格域写格子快照。global 型变异是「整图」的:整图一个类型生效,不落任何格子(棋盘持有整图变异记录,逐格变异记录恒空)。**每图一型的整图推广(2026-10-04 用户裁决)**:普通图带 region 片,Boss/精英图带 global 型——两类图各自至多一型。
**替换制**:Boss/精英图命中全局型时,布置段跳过 region 布置(该图既无 region 片、也无格子级变异标记)——L2 完美 Boss 承伤已越 ≤50% 带,region 叠全局的叠加制火上浇油,不采纳。**兜底回落**:Boss/精英图的全局候选池为空时(含第 1 关 Boss 解锁门控未达=免疫,第 1 关是过滤器不是墙的教学保护)回落 region 布置,即回批前行为(保守向后兼容,防配置缺行静默裸奔——provisional)。
@@ -130,7 +130,7 @@ region 型变异是「格子级」的:布置段往候选格域写格子快照
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| 迭代 12(调研前置) | 反制路径重定(原:首批四类型的反制道具兑现,counter_items 回填——已作废) | 2026-10-03 用户终裁:同调位反制件=层级错配失败设计,义务作废;变异应对是否以局内形态补入,待双层 BD 调研仲裁后重新立项 |
| ~~Alpha~~ **已落地** | 全局作用域变异(精英/Boss 图整图生效的变异形态) | **2026-10-04 全局作用域变异首发批落地**(迭代 14 ③号卡)——首批型=贫矿脉(案A 资源面·微贫向偏斜,替换制/恒定制/L1 Boss 免疫/三层预告);裁决全录见调研报告 §7(全局作用域设计空间调研(已退役,git 历史可查);原需求卡片已退役,git b3bbea5/01bfc15);带 A 杠杆注册全局难度批(Beta 阶段,未排期)顺带读数只测不判;精英图留词表前向位(首发零表行) |
| ~~Alpha~~ **已落地** | 全局作用域变异(精英/Boss 图整图生效的变异形态) | **2026-10-04 全局作用域变异首发批落地**(迭代 14 ③号卡)——首批型=贫矿脉(案A 资源面·微贫向偏斜,替换制/恒定制/L1 Boss 免疫/三层预告);裁决全录见调研报告 §7(全局作用域设计空间调研(已退役,git 历史可查);原需求卡片已退役);带 A 杠杆注册全局难度批(Beta 阶段,未排期)顺带读数只测不判;精英图留词表前向位(首发零表行) |
| P2 | "地形成本修饰"价目钩子启用 | 由行动经济持有;启用须随变异类型扩展,与行动经济重议按格定价的接口 |
| P2 | 变异解锁节奏复核 | "每关至少引入一个新元素"的推进节奏,随[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)定稿复核 |
| P2 | 变异格视觉标识与预告呈现 | 首批为遭遇式(未开无预告);标识位随表现层升级批 |
@@ -145,7 +145,7 @@ region 型变异是「格子级」的:布置段往候选格域写格子快照
| 直伤量 effect_value | 荆棘丛 1 / 岩浆 2 / 落石 3 | 难度曲线与Boss(provisional,加法核 [1,9]) |
| 资源面偏斜 effect_value | 贫矿脉 30(生存族下调百分比;0=中性 off 锚;总期望微降 3-5% 为 provisional 设计目标——精确方向与幅度归读数批判读只测不判) | 难度曲线与Boss(provisional,域 [0,50]——2026-10-04 首发批投值) |
变异表的结构历史(字段定义、条目归属)见 `data/schemas/variation_table.gd` 与 `data/tables/variation.tres`。字段集现行口径:标识 / 玩家可见名 / 解锁关号 / 作用域 / 效果 / 效果量 / 触发 / 布置约束 / 地形(board_types)/ 反制道具绑定(counter_items,恒空保留——回填义务 2026-10-03 作废,承载形态待迭代 12 重定)。
变异表字段集的现行口径(字段定义归变异表,条目归属即表行):标识 / 玩家可见名 / 解锁关号 / 作用域 / 效果 / 效果量 / 触发 / 布置约束 / 地形(board_types)/ 反制道具绑定(counter_items,恒空保留——回填义务 2026-10-03 作废,承载形态待迭代 12 重定)。
## 验收标准
@@ -214,7 +214,7 @@
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P2 | 探测类道具的查询接口 | 接口已由 `query_reward_identity` 只读面桥接,本系统不预写 |
| P2 | 探测类道具的查询接口 | 接口已由身份解析只读面桥接,本系统不预写 |
| P2 | 地形变异接入的格面规则 | 地形变异已定稿落地(2026-09-19 四类型两轴×两关、2026-09-20 枚举化 schema 扩批),新增类型走双方修订流程 |
## 调参表
@@ -1,7 +1,7 @@
# 棋盘生成
> 一句话:棋盘怎么生成、怎么保证可推理可清图
> 状态:已定稿(自旧体系迁移)
> 状态:已定稿(自旧体系迁移);现行为 v1.1
## 设计目的
@@ -131,7 +131,7 @@
**结论**:重试次数与图尺寸联合受成本闸约束。
```
成本闸:max_gen_attempts × board_width × board_height ≤ 成本闸护栏常数 (结构护栏常数,见 `data/schemas/board_gen_table.gd`;provisional 量级,正式值待实现期定)
成本闸:max_gen_attempts × board_width × board_height ≤ 成本闸护栏常数 (结构护栏常数,见棋盘参数表字段定义;provisional 量级,正式值待实现期定)
```
| 出口 | 何时走 | 发生什么 |
@@ -146,7 +146,7 @@
### 图类型标签与第三轴预留:主题恒定、Boss 板有座位
每张图带一个图类型标签(board_type 字段;图类型即图的主题、题材,玩家侧显示名「地图类型」)。同关卡的所有图,主题静态恒定——不存在逐图重掷主题的接口,"不会上钓鱼的当"是结构保证而非文案。词表现行 v1.1(2026-09-20 第三关内容批增补;v1.0 两词 2026-09-19 定稿):**dungeon(地牢基线)/ underdark(幽暗地域)/ deep_underdark(灰矮人矿道方向)**,现行值见 board_gen.tres(L1 = dungeon,L2 = underdark,L3 = deep_underdark);域校验 = 词表成员检查(board_gen_table.gd 词表常量),扩词随内容批走修订。这组标签是表现侧换色板与[地图战斗资源](../战斗循环/地图战斗资源.md)图类型差异化插槽的数据源。本节为数据约定,无计算。
每张图带一个图类型标签(board_type 字段;图类型即图的主题、题材,玩家侧显示名「地图类型」)。同关卡的所有图,主题静态恒定——不存在逐图重掷主题的接口,"不会上钓鱼的当"是结构保证而非文案。词表现行 v1.1(2026-09-20 第三关内容批增补;v1.0 两词 2026-09-19 定稿):**dungeon(地牢基线)/ underdark(幽暗地域)/ deep_underdark(灰矮人矿道方向)**,现行值见 board_gen.tres(L1 = dungeon,L2 = underdark,L3 = deep_underdark);域校验 = 词表成员检查(词表归棋盘参数表,扩词随内容批走修订)。这组标签是表现侧换色板与[地图战斗资源](../战斗循环/地图战斗资源.md)图类型差异化插槽的数据源。本节为数据约定,无计算。
参数表的键是关卡序号 × 节点深度,难度沿这两条轴雕刻;精英与 Boss 修饰是预留的第三条轴。本系统不定任何修饰数值,只锁定落点约定——这也是[关卡节点地图](../冒险循环/关卡节点地图.md)与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)两份文档的输入:
@@ -213,7 +213,7 @@
| shave_ring_radius | int [0, 2] | 现行 1(board_gen.tres 全行) | 先布后删剃雷半径:剃雷域=距出生中心切比雪夫距离 ≤1+r 的格(r=1 → 5×5=25 格;r=0 即只剃出生 3×3 本身=无环剃雷)。**2026-10-05 初始展开收窄批新参数**(调参项非结构冻结) |
| r0_max_cap | int [出生格数 9, 板格数一半](护栏,违例加载期致命) | 现行按板形:10×10=45 / 12×12=43 / 13×13=51 / 14×14=59(board_gen.tres) | 自然 R₀(开局免费展开格数)上限闸=合法性门;超阈整板重掷、耗尽走三分出口。板面 30% 锚(30/43/51/59)经 S1 停机呈报用户裁上调:10×10→45(r=1 自然均值 39>30 结构性倒挂;其余行 30% 锚维持)。**2026-10-05 初始展开收窄批新参数** |
结构冻结(非调参项):键结构与每关深度 0 锚行、四阶段顺序(变异布置段 2026-09-20 已移出管线)、solver_rules、可推理率的合法信息源枚举、及格线域下限 0.85、成本闸常数(结构护栏,登记于 registry board_gen_table 条目,调整走文档修订)、先布后删语义。
结构冻结(非调参项):键结构与每关深度 0 锚行、四阶段顺序(变异布置段 2026-09-20 已移出管线)、solver_rules、可推理率的合法信息源枚举、及格线域下限 0.85、成本闸常数(结构护栏,登记在案,调整走文档修订)、先布后删语义。
## 验收标准
@@ -225,10 +225,10 @@
- 可清图:每个安全格都能从出生区沿安全格八向连通到达;合法性闸先于质量评分。
- 及格线:每张图实测可推理率 ≥ 该行及格线;任何行及格线 ≥ 0.85。
- 重试有界:实际重试次数落在 [1, max_gen_attempts];同一次生成内的重试不开新随机流。
- 成本闸:max_gen_attempts × 宽 × 高超过成本闸护栏常数(结构护栏常数,见 `data/schemas/board_gen_table.gd`)的行加载期被拒;玩法代码零墙钟依赖、零全局随机函数。
- 成本闸:max_gen_attempts × 宽 × 高超过成本闸护栏常数(结构护栏常数,见棋盘参数表字段定义)的行加载期被拒;同种子同参数在任意机器上出图逐位一致。
- 降级诚实:零合法板照常出图,必带非法产物标记与最高级告警;进程不死锁,玩家界面得到诚实告知。
- 加载期校验:布雷数下限、计数可行性、标签非空等违例组合加载期致命。
- 黄金图回归:手工核验小板集的可推理率输出与登记整数对逐位相等(整数对比对,禁浮点容差);换生成参数(推理定义、信息源、定义版本号)必须重制向量并入版本库。
- R₀≤cap 闸(2026-10-05 初始展开收窄批):每张出图自然 R₀ ≤ 该行 r0_max_cap(合法板),或带非法产物标记(accept_worst 诚实降级);闸触发次数入指标集 r0_cap_rejected。
- R₀≤cap 闸(2026-10-05 初始展开收窄批):每张出图自然 R₀ ≤ 该行 r0_max_cap(合法板),或带非法产物标记(接受最不坏·诚实降级);闸触发次数入指标集 r0_cap_rejected。
- 剃雷域参数化(同批):剃雷域=出生中心切比雪夫 ≤1+shave_ring_radius 的方域,出图 ring_removed ≤ 域内候选危险格数 (3+2r)²−9;同种子布雷序列与收窄前逐位一致(零 RNG 消费变化)。
- 表实一致:配置表字段定义与 `data/schemas/board_gen_table.gd` 一致。
- 表实一致:配置表字段与棋盘参数表字段定义一致。
+27 -10
View File
@@ -1,7 +1,7 @@
# 行动经济
> 一句话:每轮行动点怎么花、停手与刷新的代价
> 状态:已定稿(自旧体系迁移)
> 状态:已定稿(自旧体系迁移);现行为 v1.2
## 设计目的
@@ -79,7 +79,7 @@
### 停手与储备行动点
停手是玩家在挖掘期任意时刻的主动决定:立即结束本轮,剩余行动点按 1:1 原值存入储备池——基线没有乘数、没有加成。图没开完,下一轮接着在同一张图上挖。停手不是收入来源,而是纯粹的风险管理工具:它买到的是"退出权",不是战斗收益。停手意图经排雷玩法的批次提交入口进入账本(保证单一结算序),由账本仲裁后自行外发轮次结束消息。**转存率参数化(迭代 22 秘宝批反哺写入,2026-10-12)**:转存率实现为 `finish_round_stop(transfer_pct)` 参数面——缺省 100=1:1 与旧口径逐位一致;秘宝「储备罐」注入 125=1.25:1(整数除法向下取整——每 4 点剩余 AP 多转 1 点 RAP,零头顺延向下);无装配时行为与参数化前逐位相同。转存率归秘宝操作包络参数,本账本的仲裁与发射结构不变。
停手是玩家在挖掘期任意时刻的主动决定:立即结束本轮,剩余行动点按 1:1 原值存入储备池——基线没有乘数、没有加成。图没开完,下一轮接着在同一张图上挖。停手不是收入来源,而是纯粹的风险管理工具:它买到的是"退出权",不是战斗收益。停手意图经排雷玩法的批次提交入口进入账本(保证单一结算序),由账本仲裁后自行外发轮次结束消息。**转存率参数化(迭代 22 秘宝批反哺写入,2026-10-12)**:转存率实现为停手收轮时的转存率参数——缺省 100=1:1 与旧口径逐位一致;秘宝「储备罐」注入 125=1.25:1(整数除法向下取整——每 4 点剩余 AP 多转 1 点 RAP,零头顺延向下);无装配时行为与参数化前逐位相同。转存率归秘宝操作包络参数,本账本的仲裁与发射结构不变。
储备池的生命周期以一场战斗为界:战斗内跨轮次、跨雷图一直累积;战斗结束立刻清零。清零必须在界面上可见,防止玩家觉得"攒的钱凭空没了"。MVP 阶段储备池没有上限。
@@ -91,9 +91,22 @@
停手与刷新合起来构成这本账的基线节拍:满行动点一轮停手,正好攒够一次刷新。行动点修正队列会扰动该节拍的实际兑现(+1 轮可多挖一格),不变式本身不动;对账归曲线调参批。
### 换图系专长对储备与刷新的改写(设计定稿,待实现)
换图系两件基座分别改写 RAP 的来源与刷新价格。它们只给持有专长的玩家增加规则,不改变无专长时的基线账本。
| 专长 | 规则 | 不变边界 |
|---|---|---|
| 掘进储能 | 每次付费挖格被受理后,额外获得表值 RAP;安全格与危险格同口径 | 连锁展开只随首格的一次付费动作结算,不逐格追加;封印、移动、刷新不产 RAP |
| 折价换图 | 弃图刷新的 RAP 成本为基线的 50% | 只改价格;AP、轮次、每轮刷新上限与换图流程不变 |
两件基座可以同时持有并叠加:付费挖格照常产 RAP,刷新同时按半价扣费。两条规则作用在不同账户入口,不互相覆盖,也不设互斥。刷新仍须储备达到折后门槛才受理;储备不足或超过每轮次数上限时拒绝,状态不变。RAP 仍在战斗结束清零,刷新本身不返 RAP。
无掘进储能时,挖格不新增 RAP;无折价换图时,刷新成本仍读全局基值。后续输出、生存与资源专长如何使用成功刷新这个事实,归[战斗结算](../战斗循环/战斗结算.md)。
### 下轮行动点修正(格面资源八型批 2026-09-20)
格面资源八型批(2026-09-20)新增 action 型格面资源:正向行 ap_up = 下轮行动点 +1,负向行 ap_drain(迟滞)= 下轮行动点 −1。修正不在授予当轮生效:结算期经 `action_economy.queue_ap_delta` 排队(同轮多笔按净值累加),下一轮开始时(notify_round_start)一次性兑现——兑现值 = 基线 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)。
跨轮携带量口径随之改写:RAP 仍是唯一显式携带的系统资源(停手转存、玩家可见可花),行动点修正队列是第二携带量——一次性、自动清零、不可主动消费。
@@ -132,13 +145,13 @@
4. **清图奖励大于零**,并预留按图类型差异化分支的结构位。
5. **清图支配性**:推到底清图的期望收益,不低于"确定格挖完就弃图刷新"的期望收益——否则清图玩法会被刷新策略淘汰。
**结论**:五条契约由战斗结算作为唯一对账责任方闭合验收;概率侧由棋盘生成的可推理率参数锚定;条款3 数值层由 β 比值带 [1.2, 2.5] 判读看护(常设重跑入口 `tools/clear_dominance_audit.py`),封印边际结构层由条件二封印溢价接替(机制载体 settlement.flag_hit_premium)。
**结论**:五条契约由战斗结算作为唯一对账责任方闭合验收;概率侧由棋盘生成的可推理率参数锚定;条款3 数值层由 β 比值带 [1.2, 2.5] 判读看护(清图支配性审计为常设重跑项),封印边际结构层由条件二封印溢价接替(机制载体 settlement.flag_hit_premium)。
推导:
```
条款 3(2026-10-05 判式换代批重立——三层口径并行立,2026-10-04 用户裁 P2;
设计权威 = 方案报告 §四/§九(已退役,git 1da9a41;现行判式口径=本文条款③))
设计权威 = 方案报告 §四/§九(已退役;现行判式口径=本文条款③))
条款 2(封印标中锚,既有不动):封印标中的减益档位必须严格高于最低档安全格增益。
@@ -172,7 +185,7 @@
η 读数(行为面,非判式——2026-10-04 用户裁 P1 乙降格读数):
η = flag_hit ÷ flag_placed(分母 = 全部封印数,2026-10-04 grill Q5 裁),
标空率 = flag_miss ÷ flag_placed 次读数分列。字段随 runsum 五字段承载
标空率 = flag_miss ÷ flag_placed 次读数分列。字段随结算摘要五字段承载
(flag_placed/flag_hit/flag_miss/标中兑付合计/标空惩罚合计——结算流水聚合)。
条款 5(清图支配性,不动):
@@ -181,9 +194,9 @@
| 变量 | 含义 | 锚定来源 |
|---|---|---|
| E_dig(u) | 单安全格期望收益当量(77 权重池加权,随敌方单位数 u 缩放 side-wide 项) | 战斗结算的效果对照表 + `tools/clear_dominance_audit.py` |
| E_dig(u) | 单安全格期望收益当量(77 权重池加权,随敌方单位数 u 缩放 side-wide 项) | 战斗结算的效果对照表 + 清图支配性审计读数 |
| V_mine(u) | 单雷期望惩罚当量(九型负向字典同池加权) | 同上 |
| ρ̂ | 雷率:名义 0.25 与实测幸存雷率(10×10 ≈ 0.129 / 12×12 ≈ 0.157)双口径并报,主表用名义(保守) | board_gen.tres + board_stat_probe 读数 |
| ρ̂ | 雷率:名义 0.25 与实测幸存雷率(10×10 ≈ 0.129 / 12×12 ≈ 0.157)双口径并报,主表用名义(保守) | board_gen.tres + 棋盘统计读数 |
| q | 玩家对目标格为雷的置信 | 玩家侧行为量(可推理格 q ∈ {0,1}) |
| β 带 | V_mine/E_dig 判读带 [1.2, 2.5] | 本文条款③重立条 |
| δ | 封印溢价系数 0.25 provisional | settlement.tres flag_hit_premium = 125 |
@@ -236,9 +249,11 @@
| move_ap_cost | int | 表值(冻结 0) | 移动免费——冻结,非调参项 |
| chain_ap_cost | int | 表值(冻结 0) | 连锁免费——冻结,非调参项 |
| refresh_cap_per_round | int | 表值(provisional,结构位预留) | 每轮刷新上限,开轮重置计数 |
| (跨表)掘进储能产 RAP | int [1, 64] | 设计定稿,待入 `feats.tres` | 每次受理的付费挖格兑付一次;连锁不逐格追加 |
| (跨表)折价换图成本率 | percent | 50%(结构已裁,待入 `feats.tres`) | 只改持有者的刷新价格;双基座叠加时不覆盖掘进储能 |
| (跨表)跨冒险行动点加成 | int [0,10] / int [0,99],冒险表 | adventure_table bonus_ap_flat / bonus_cap_ap(provisional) | 三选一 AP 件兑现值与全冒险次数上限;加成轴语义见「跨冒险行动点加成轴」节 |
字段名注记:2026-09-18 措辞完成批已全链对齐正式名——表字段与本文行名统一为 `refresh_cap_per_round`,行动点入口统一为 `request_refresh`(registry naming_unification 兑现,切片临时名零残留)。
字段名注记:2026-09-18 措辞完成批已全链对齐正式名——表字段与本文行名统一为 `refresh_cap_per_round`,刷新入口命名与表字段口径对齐(命名统一登记兑现,切片临时名零残留)。
跨字段校验:任一动作成本禁止超过每轮预算,违反即加载期致命(fail-fast)。各域上界(8 / 64)是 MVP 量级护栏而非推导界,随调参需要修订。
@@ -250,10 +265,12 @@
- 仲裁唯一:最后 1 点行动点挖开最后一个安全格时,判清图(剩余清零 + 换图 + 清图奖励),不判耗尽;输出恰一条。
- 停手 1:1:剩余 3 点停手,储备池加 3,下轮同图继续。
- 刷新规则:储备不足时刷新被拒绝且状态不变;同一轮第 3 次刷新被拒绝;刷新不重置行动点、不发清图奖励、旧图上的封印印记失效。
- 掘进储能:持有专长时,每次受理的付费挖格额外产一次 RAP,安全格与危险格同口径,连锁展开不逐格追加;无专长时挖格不新增 RAP。
- 折价换图与双基座:持有折价换图时按基线的 50% 扣费;与掘进储能同时持有则两条规则都生效,刷新仍受储备门槛与每轮次数上限约束。
- 储备清零:战斗结束信号到达后储备池归 0,行动点进入休眠。
- 跨字段校验:任一动作成本配置超过每轮预算时,加载期致命中止。
- 零随机:同一动作序列驱动两个独立行动点实例,轮次结束记录与储备轨迹逐项相同。
- 表实一致:配置表字段与 `data/schemas/action_economy_table.gd` 逐字段一致。
- 表实一致:配置表字段与行动经济表字段定义逐字段一致。
## 待办
+17 -18
View File
@@ -55,13 +55,13 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
三区各管一块:
| 区 | 位置与尺寸 | 管什么 |
| 区 | 位置与几何 | 管什么 |
|---|---|---|
| B 战斗画面 | 全宽 × 270px | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 |
| C 角色信息 | 左下 240×530 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
| D 扫雷图 | 中下约 1036×530(原右下面板删除后横向加宽) | 行动点条与按钮组、棋盘本体、toast 槽 |
| B 战斗画面 | 全宽,高读布局表 `stage_h` | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 |
| C 角色信息 | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
| 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 档由引擎等比承载、零重排。
@@ -74,9 +74,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 变量 | 含义 | 现行值 |
|---|---|---|
| N | 棋盘边长格数 | 读[棋盘生成](../排雷循环/棋盘生成.md)(现行四档;事实源 board_gen.tres) |
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条 44px 得出,本表不复制现算值 |
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条高(读 `ap_bar_h`)得出,本表不复制现算值 |
装板可行性由上面的不等式保证;格边长的现行算例与可读下限见 [battle.md](../../ux/battle.md)。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
装板可行性由上面的不等式保证;格边长的可读下限见 [battle.md](../../ux/battle.md),算例由样张渲染脚本按布局表现算、不入文档。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
### 提示与弹层:轻提示随起随消、重弹层排队进出
@@ -102,7 +102,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 棋盘(封印印记、小人位置) | 意图批次调用返回后重查 |
| 行动点 / 储备 | 每次写路径回执后重读 |
| 状态效果 | 绑定结算编排完成时点 |
| 已开格奖励身份 | 已开格揭显时经身份解析只读回调查询(`query_identity`,组合根注入) |
| 已开格奖励身份 | 已开格揭显时经身份解析只读回调查询(组合根注入) |
就地更新指只改变化的字段、不整块重建界面——整块重建会把敌人立绘死亡灰化这类就地状态吞掉。
@@ -134,7 +134,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 信息内容 | 只表达格面资源的类型(九类格面资源归五组色相呈现,色相/明度/画法规格归 [art-bible](../../../art/art-bible.md) §4.2);不表达极性(安全/雷)、不表达具体收益档位 |
| 生命周期 | 资源身份装板期摇定(微光只是显示,不改任何游戏数据)→ 未开 ∧ 在 8 格内 → 亮;格子被挖开/连锁开/踩雷 → 灭;角色走远出圈 → 灭,走回 → 复亮;换图随旧图全重置 |
| 关联与边界 | 信息本体(格面资源身份)归[地图战斗资源](../战斗循环/地图战斗资源.md);棋盘格态与动作合法性归[排雷玩法](../排雷循环/排雷玩法.md);与「接触带微光」同圈不同职责——接触带微光=可挖域的交互反馈光(「这里能挖」),类型微光=相邻格的类型先读(「这格是什么类型」) |
| 实装状态 | 呈现契约已定稿(五组色相经样张 v2 冻结 2026-09-28);**已实装(迭代 30 卡① 2026-10-17 入库,合并 `7ffd1dc0`)**——2026-09-30 核证的「呈现层仅接触带微光在跑,类型微光无独立实现」状态自此注销;在役实现(五组色相 = ui_kit TG_GLOW_*、格面微光子件 + 距离场 shader)与验收转正见本节末「验收标准」类型微光行,登记面归 [待办清单](../../../../待办清单.md)「未开格类型徽章呈现时机」条(已完成) |
| 实装状态 | 呈现契约已定稿(五组色相经样张 v2 冻结 2026-09-28);**已实装(迭代 30 卡① 2026-10-17 入库)**——2026-09-30 核证的「呈现层仅接触带微光在跑,类型微光无独立实现」状态自此注销;在役实现(五组色相 = 呈现套件色相常量、格面微光子件 + 距离场渲染)与验收转正见本节末「验收标准」类型微光行,登记面归 [待办清单](../../../../待办清单.md)「未开格类型徽章呈现时机」条(已完成) |
口径史注记(防复发):本概念曾只活在待办清单登记与 art-bible 复述里,「常显」「全部未开格」等无范围表述于 2026-09-30 酿成理解事故;用户裁决「英雄周围 8 格」为正确口径并落位本节。教训=概念必须落地权威文档,零散复述不是定义。
@@ -148,7 +148,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|---|---|
| 定义与作用范围 | 接触带微光 = 可挖域的交互反馈光(「这里能挖」)——英雄格与可挖域(8 向相邻未开格;概念本体与动作合法性归[排雷玩法](../排雷循环/排雷玩法.md))接触面上的呼吸微光;与「未开格类型微光」同圈不同职责(类型微光=「这格是什么类型」,本光=「这里能挖」) |
| 画法 | 光画英雄格周界空间侧:正交邻墙=该侧整条边,斜向邻墙=共享角点+向两侧各延伸半条边;光只画英雄格一侧、不进墙格;色相=蓝族浅一档——画法细则归 [art-bible](../../../art/art-bible.md) §3.2 |
| 生命周期 | 挖掘期呈现且呼吸(挖掘期唯一在动元素——art-bible §2.1;相位门控 `set_dig_glow_enabled`,结算期停息);减动效开启=呼吸停格(静态保留指界功能);换图/装板随新可挖域重算 |
| 生命周期 | 挖掘期呈现且呼吸(挖掘期唯一在动元素——art-bible §2.1;相位门控,结算期停息);减动效开启=呼吸停格(静态保留指界功能);换图/装板随新可挖域重算 |
| 关联与边界 | 逐格指认=悬停金框(pattern 4 悬停预承诺——微光是常驻指界、悬停是意图预承诺,两概念分工);不携带类型/极性信息(那是类型微光的职责);零信息写入(纯呈现层) |
| 实装状态 | 已实装(呈现层现行在跑——2026-09-30 核证;程序化占位,画法终稿随样张批) |
@@ -163,9 +163,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 关联与边界 | 与「未开格类型微光」同边界——灯光区起步半径=微光环 8 格(同圈起步,实测后可调);格态数据归[排雷玩法](../排雷循环/排雷玩法.md);黑暗区域渲染规格(暗淡态明度、迷雾过渡)归 [art-bible](../../../art/art-bible.md) 修订随实施批;初始展开收窄不在本案(后置与 Alpha 差异化批窗口合并再议) |
| 实装状态 | **已实装**(2026-10-03 迭代 11 第④批已入库;四裁决逐条对账与验收线转正见本节末「实施状态」注记;渲染规格 = [art-bible](../../../art/art-bible.md) §6.5) |
配套拍板(同日):魔法提灯=灯甲形态(光照圈外扩 +2 格,flat 整数档)随迷雾立项;**类型微光不随视野扩大**(类型先读仍只贴身 8 格,微光锚=可挖域);提灯载体归 aux_items 新行(登记原文——2026-10-07 用户令改道为独立卡,2026-10-08 裁案A=秘宝·同调位新行落位,见下方实施状态注记;**2026-10-09 用户裁退役**——新目录 15 件未收提灯,fog_lantern 数据行与卡④接线拆除归退役删行批〔实装批〕,功能由新目录普通档「阔视野带」承接,待办清单登记条在册),呈现面挂「资产升档重产批」(挂账不动)。
配套拍板(同日):魔法提灯=灯甲形态(光照圈外扩 +2 格,flat 整数档)随迷雾立项;**类型微光不随视野扩大**(类型先读仍只贴身 8 格,微光锚=可挖域);提灯载体归 aux_items 新行(登记原文——2026-10-07 用户令改道为独立卡,2026-10-08 裁案A=秘宝·同调位新行落位,见下方实施状态注记;**2026-10-09 用户裁退役**——新目录 15 件未收提灯,提灯数据行与卡④接线拆除归退役删行批〔实装批〕,功能由新目录普通档「阔视野带」承接,待办清单登记条在册),呈现面挂「资产升档重产批」(挂账不动)。
**实施状态(2026-10-03 迭代 11 第④批已入库)**:迷雾遮蔽+菱形灯光区跟随+暗淡记忆态上线,探针断言顺带扩(ui_input_probe case⑮)。四裁决逐条对账——①案甲视野迷雾立项(纯呈现层):迷雾开关分层验证可用(呈现态布尔,off 态=无迷雾基线),同板同动作序列与 MC 逐项比对逐位一致;②暗淡记忆态:区外已开格灰显+数字可读,明度按 [art-bible](../../../art/art-bible.md) §6.5 写入文档值(记忆板 #3A3A3A/记忆墨 #C4C4C4,provisional);③灯光半径=微光环 8 格同圈:LIGHT_RADIUS=8 参数化(曼哈顿菱形,常量);④提灯 +2、微光不扩:FOG_LANTERN_BONUS=+2(落码常量实名——本条旧记 LIGHT_RADIUS_LANTERN_BONUS 系文档侧拟名,随卡④入库对齐),类型微光仍贴身 8 邻格零改动。**〔2026-10-05 迭代14⑤接线注记〕公共接线面已就位**——`board_view.set_fog_lantern(enabled)`(true=半径 8+2、false=复原 0;写入即 refresh 重算雾态),半径算式消费 `_fog_lantern` 迷雾批已在位;ui_input_probe case⑮ 增接线断言(开=dist 9 格转可见+lantern=2/关=复原)。**〔2026-10-08 迭代 19 卡④入库注记〕载体到位+接线闭环=功能可达**——载体=秘宝·同调位新行 `fog_lantern`「魔法提灯」(treasure_items.tres 表尾追加,desc「灯光区半径+2」,slot_cost 1、营地秘宝匣同调装配获渠,行字段=用户「按推荐」全收);生产路径=runner set_attunement→enc.attunement→begin_battle 注入 orchestrator 装配集→公共只读出口 `attune_has`→组合根呈现桥 `main._sync_fog_lantern` 每战首轮装配链**显式重算含否**(🔴 复原语义=禁战斗结束隐式复原——卸装/未装配写 0,断点续玩/跨关天然正确);未装配=false 写 0=缺省零扰动基线逐位不变。顺带测试:case⑮ 载体级段(装配→lantern=2+dist R+1 转可见+卸装复原)+smoke 行字段/同调目录投影(九件→十件)/装配贯通断言+存档回归 Part 6+单测 fog_lantern_wiring(run_all 17→18 套)。原「提灯载体归 aux_items 新行随 Alpha 信息件重定批」登记去向就此注销(改道留痕=需求卡片-提灯载体道具批-2026-10-07 §一)。验收线(本篇「验收标准」视野迷雾行)逐条转正式验收:区内完整渲染/区外已开格灰显可读/区外未开格不可见/灯光区随移动跟随/两态结算逐位一致;实机目检人闸(迷雾观感/记忆态可读性/移动跟随流畅度)随出口闸。黑暗区域渲染规格承载 = art-bible §6.5(v3.5)。
**实施状态(2026-10-03 迭代 11 第④批已入库)**:迷雾遮蔽+菱形灯光区跟随+暗淡记忆态上线,输入探针断言顺带扩(迷雾案)。四裁决逐条对账——①案甲视野迷雾立项(纯呈现层):迷雾开关分层验证可用(呈现态布尔,off 态=无迷雾基线),同板同动作序列与 MC 逐项比对逐位一致;②暗淡记忆态:区外已开格灰显+数字可读,明度按 [art-bible](../../../art/art-bible.md) §6.5 写入文档值(记忆板 #3A3A3A/记忆墨 #C4C4C4,provisional);③灯光半径=微光环 8 格同圈:半径 8 参数化(曼哈顿菱形,常量);④提灯 +2、微光不扩:提灯加成 +2,类型微光仍贴身 8 邻格零改动。**〔2026-10-05 迭代14⑤接线注记〕公共接线面已就位**——公共提灯开关接口(开=半径 8+2、关=复原 0;写入即重算雾态),迷雾批已在位消费该值;输入探针增接线断言(开=距离 9 格转可见+提灯 2/关=复原)。**〔2026-10-08 迭代 19 卡④入库注记〕载体到位+接线闭环=功能可达**——载体=秘宝·同调位新行「魔法提灯」(treasure_items.tres 表尾追加,desc「灯光区半径+2」,slot_cost 1、营地秘宝匣同调装配获渠,行字段=用户「按推荐」全收);生产路径=冒险装配→战斗装配集注入→公共只读出口查装配含否→组合根呈现桥每战首轮装配链**显式重算含否**(🔴 复原语义=禁战斗结束隐式复原——卸装/未装配写 0,断点续玩/跨关天然正确);未装配=零加成(缺省零扰动基线逐位不变)。顺带测试:载体级迷雾案(装配→提灯 2+距离 R+1 转可见+卸装复原)+冒烟行字段/同调目录投影(九件→十件)/装配贯通断言+存档回归 Part 6+单测提灯接线(全套 17→18 套)。原「提灯载体归 aux_items 新行随 Alpha 信息件重定批」登记去向就此注销(改道留痕=需求卡片-提灯载体道具批-2026-10-07 §一)。验收线(本篇「验收标准」视野迷雾行)逐条转正式验收:区内完整渲染/区外已开格灰显可读/区外未开格不可见/灯光区随移动跟随/两态结算逐位一致;实机目检人闸(迷雾观感/记忆态可读性/移动跟随流畅度)随出口闸。黑暗区域渲染规格承载 = art-bible §6.5(v3.5)。
### 营地:可点击场景图(旧四柜台并排形态已随卡④改版取代)
@@ -196,9 +196,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 上游 | [排雷玩法](../排雷循环/排雷玩法.md) | 棋盘只读投影(格态、数字、封印印记、小人)与移动路径查询;玩家意图批次经组合根提交 |
| 上游 | [行动经济](../排雷循环/行动经济.md) | 行动点 / 储备 / 相位只读值;刷新入口可用性预判加返回值兜底 |
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 明细流水是结算明细带的统一数据源(现行 9 类行封闭协议);段序编排信号经组合根接线 |
| 上游 | [敌人系统](../战斗循环/敌人系统.md) | 英雄与敌人的只读投影(血量、顺位、摇定动作〔pending_skill/type/name〕、敌方增益 effects、存活——经战斗结算呈现快照;敌人技能池批批 2 定稿:当前拍/行动环进度字段拆除,池与权重不公开) |
| 上游 | [敌人系统](../战斗循环/敌人系统.md) | 英雄与敌人的只读投影(血量、顺位、摇定动作〔技能标识·类型·名称〕、敌方增益、存活——经战斗结算呈现快照;敌人技能池批批 2 定稿:当前拍/行动环进度字段拆除,池与权重不公开) |
| 上游 | [增益状态](../战斗循环/增益状态.md) | 状态投影经战斗结算呈现面取得,零直读 |
| 上游 | [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格奖励身份投影(`query_identity` 只读回调,组合根注入)零直读 |
| 上游 | [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格奖励身份投影(只读回调,组合根注入)零直读 |
| 上游 | [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链、当前节点、跨战斗生命携带的读数;胜利后推进 |
| 下游 | [战斗表现](../战斗循环/战斗表现.md) | 交付三区布局、明细带呈现位、敌人呈现语义(站桩形态);它只叠加动画密度与演出 |
| 下游 | [音频](音频.md) | 挂点事件源(明细行类型流、toast 触发);MVP 无音频 |
@@ -218,7 +218,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格的奖励身份常读;未开格类型微光(英雄周围 8 格内的未开格显示资源类型——概念定义见本篇「未开格类型微光」节) |
| [秘宝](../跨局循环/秘宝.md) | 装配件与充能常读;使用交互与目标拾取;整图叠加层信息呈现(在役旧框架——随实装批整体替换,秘宝.md v1.7 同款口径) |
| [棋盘生成](../排雷循环/棋盘生成.md) | 零合法板的诚实告知(数据面经战斗结算上抛,本系统只管提示形态) |
| [地形变异](../排雷循环/地形变异.md) | 全局作用域型三层预告(2026-10-04 首发批——语义现行事实源=地形变异 §全局作用域型):①开战横幅一行(整图变异非空时显示;减动效=静态驻留,轮横幅先例同款双态)②HUD 常驻角标(C 区战斗全程可见防中局遗忘,空态整节隐藏=奇物段先例;摆位 provisional)③帮助手册一行(零交互教学,承载=玩法帮助文案块);数据面经呈现快照 board_variants 投影(核心→呈现唯一读取面),普通图三层恒空态零打扰 |
| [地形变异](../排雷循环/地形变异.md) | 全局作用域型三层预告(2026-10-04 首发批——语义现行事实源=地形变异 §全局作用域型):①开战横幅一行(整图变异非空时显示;减动效=静态驻留,轮横幅先例同款双态)②HUD 常驻角标(C 区战斗全程可见防中局遗忘,空态整节隐藏=奇物段先例;摆位 provisional)③帮助手册一行(零交互教学,承载=玩法帮助文案块);数据面经呈现快照的整图变异投影(核心→呈现唯一读取面),普通图三层恒空态零打扰 |
## P0 最小闭环
@@ -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 | 上限约束:三五秒即收 |
| font_min_board / font_min_detail / font_min_tooltip | 1280×800 基座 | 18 / 14 / 13 px | 可访问性基线 |
| reduce_motion_scale | 每段动画 | 0.5 或跳终态 | 减动效开关系数 |
| detail_band_height | 结算明细带 | 24 / 84 px | 摘要 / 实时流两态高度 |
结构冻结(非调参项,改动走文档修订):三区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 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。
- 已开 / 未开格态在灰度截图下可逐格分类;已引爆危险格"可通行可读"(已开底加巢穴纹、可作移动目标)。
- 类型微光:微光只在英雄周围 8 格内的未开格显示;出圈即灭、入圈即亮;已开格与封锁格恒无微光;微光呈现不随格是雷是安变化(不泄危险格与安全格)。(迭代 30 卡①实装后转正式验收)
@@ -1,13 +1,13 @@
# 存档序列化
> 一句话:存档契约与版本迁移
> 状态:已定稿(2026-09-21 存档序列化批落地);现行为 v1.1
> 状态:已定稿(2026-09-21 存档序列化批落地);现行为 v1.2
## 设计目的
本系统管的是"关掉游戏之后,玩家的东西还在不在"。序列化(本文含义:把游戏运行中的数据写成可存盘的档案,下次启动再原样读回来)与版本迁移(本文含义:游戏更新后,旧版本写下的存档要能被新版本继续读懂,必要时把旧结构改写成新结构)合起来,就是"存档契约"这门生意的全部。
这件事是[核心循环](../../01-核心循环.md)里"存档在变厚"那条故事线的底层承载:钥石攒进存档、倒下也不丢——写在纸面上就是本系统的职责。本系统在切片里的落地面是 `core/save_manager.gd`(SaveManager,Core 层零 UI、零玩法依赖),本文描述的就是这份契约:存什么、何时读写、版本怎么走、坏了怎么办。
这件事是[核心循环](../../01-核心循环.md)里"存档在变厚"那条故事线的底层承载:钥石攒进存档、倒下也不丢——写在纸面上就是本系统的职责。本系统在切片里的落地面是存档管理器:Core 层,零界面依赖、零玩法依赖。本文描述的就是这份契约:存什么、何时读写、版本怎么走、坏了怎么办。
## 系统范围
@@ -48,7 +48,7 @@
### 基础层身份:无上游依赖的承载方
SaveManager 不依赖任何玩法系统就能工作:它是被动的承载方,任何系统要把数据活过单局,都向本系统登记持久化归属,而不是本系统去追认每个系统。这条身份可验证:SaveManager 不引入任何玩法脚本、不碰数值表(ConfigDB)与随机服务(RngService),可以独立实例化、独立读写验证(冒烟独立段承载)。
存档系统不依赖任何玩法系统就能工作:它是被动的承载方,任何系统要把数据活过单局,都向本系统登记持久化归属,而不是本系统去追认每个系统。这条身份可验证:存档系统不引入任何玩法脚本、不碰数值表与随机服务,可以独立实例化、独立读写验证(冒烟独立段承载)。
### 存什么:表与档分家,局内系统资源不入档
@@ -56,13 +56,13 @@ SaveManager 不依赖任何玩法系统就能工作:它是被动的承载方
第二条边界是反面清单:局内资源不入存档。行动点、储备行动点、道具库存、生命、GP 全部单局清零——各归各的系统管,没有局内数值需要持久化。若未来某局内资源要开放跨局携带(如储备行动点跨战斗携带),须修订其池规则并向本系统登记持久化归属,禁止静默超域。
**唯一豁免 = `breakpoint` 断点域**(2026-09-29 迭代 8 卡①顺带修订):断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(通关/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描顺带收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。断点顶层键(27 键+2026-10-27 迭代 39 获得日记键扩列至 28)的登记单源=`core/adventure_runner.gd` 内 `const BREAKPOINT_FIELDS`(键→回退策略标签);写读已换代为各件自交片段——主角账/停点件/关卡件各自写读自己的键(缺键回退策略随领域知识归各件),冒险应用层是唯一拼盘落盘口(收齐各件片段拼成顶层快照后写盘,写触发点不变)。**关卡完成停点的同调未用行留存**(2026-10-27 迭代 39 副卡边界裁定):`aux_items` 键在此停点形态含同调未用行(关卡末清空改选择性保留——「同调行始终留在 treasure_inventory」与「键值形态逐位不变」在关卡完成停点互斥,采行留存;玩家可见 C 区投影零变化,24 停点充能读数逐位一致)。
**唯一豁免 = `breakpoint` 断点域**(2026-09-29 迭代 8 卡①顺带修订):断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(通关/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描顺带收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。断点顶层键(27 键+2026-10-27 迭代 39 获得日记键扩列至 28)的登记单源=冒险应用层的断点键表(键→回退策略;现行契约见架构文档「冒险域拆分」节);写读已换代为各件自交片段——主角账/停点件/关卡件各自写读自己的键(缺键回退策略随领域知识归各件),冒险应用层是唯一拼盘落盘口(收齐各件片段拼成顶层快照后写盘,写触发点不变)。**关卡完成停点的同调未用行留存**(2026-10-27 迭代 39 副卡边界裁定):`aux_items` 键在此停点形态含同调未用行(关卡末清空改选择性保留——「同调行始终留在 treasure_inventory」与「键值形态逐位不变」在关卡完成停点互斥,采行留存;玩家可见 C 区投影零变化,24 停点充能读数逐位一致)。
**三存档位(迭代 33 卡①,2026-10-22 反哺写入)**:存档是 3 个独立的 JSON 文件(`user://save_slot_1/2/3.json`,槽文件名只经 `slot_path()` 单点派生,槽 1 禁复用旧单档名),字典键一律字符串。写盘目标=当前激活槽的文件;启动(boot)不读任何槽文件——启动态=无激活态,不读写;激活态由选档界面的「新档」(`create_slot`)或「进档」(`activate_slot`)建立(选档界面归迭代 33 卡③)。数据模型(现行 = v6;示例值全为零值默认态):
**三存档位(迭代 33 卡①,2026-10-22 反哺写入)**:存档是 3 个独立的 JSON 文件(三个独立存档文件,槽文件名只经存档系统单点派生,槽 1 禁复用旧单档名),字典键一律字符串。写盘目标=当前激活槽的文件;启动(boot)不读任何槽文件——启动态=无激活态,不读写;激活态由选档界面的「新档」或「进档」建立(选档界面归迭代 33 卡③)。数据模型(现行 = v7;示例值全为零值默认态):
```json
{
"version": 6,
"version": 7,
"meta": {
"keystone_balance": 0,
"item_unlocks": {},
@@ -95,28 +95,28 @@ SaveManager 不依赖任何玩法系统就能工作:它是被动的承载方
| `attune_refresh_level` | 营地刷新升级(Y 源)等级(v4) | [秘宝](../跨局循环/秘宝.md)(三选一刷新双源) |
| `tree_nodes` | 天赋树已购等级(node_id→int,v5) | [天赋树](../跨局循环/天赋树.md) |
| `fork_sides` | Fork 组激活侧(fork_group→a/b,v5) | [天赋树](../跨局循环/天赋树.md) |
| `tree_struct` | 树结构版本标记(=TREE_STRUCT_VERSION,迭代 21 T5;缺失 = 换代前结构,加载废弃重置) | [天赋树](../跨局循环/天赋树.md) |
| `tree_struct` | 树结构版本标记(迭代 21 T5;缺失 = 换代前结构,加载废弃重置) | [天赋树](../跨局循环/天赋树.md) |
| `recent_summaries` | 最近冒险摘要副本,最新在前,最多 10 条 | [冒险结构](../冒险循环/冒险结构.md) |
| `first_adventure_done` | 首冒险完成标记(新档默认 false;旧档缺省按已完成兜底) | [元进度](../跨局循环/元进度.md) |
| `adventure_total` | 冒险总局数终身计数(开局即计、含放弃与失败,v6) | [生涯](../跨局循环/生涯.md) |
| `adventure_cleared` | 冒险成功局数终身计数(三关全通才计 1,v6;秘宝条件解锁计数键) | [生涯](../跨局循环/生涯.md) |
| `vision_used` | 真视类工具累计使用次数终身计数(迭代 22 新增——信标网络解锁行为条件;**v6 域后扩列补零、不升版本**;写入唯一经 `add_vision_used`,冒险收束聚合入账;断点随件键=`vision_counter_used`,续玩回填缺省 0) | [秘宝](../跨局循环/秘宝.md) |
| ~~`forge_levels`~~ | ~~属性锻造三档等级~~ | **已退役**(迭代 21 T5:行族/访问器/域随攻防所有权切分删除——`save_manager.gd:12-14/:399-401` 白名单收窄;继任 = 锻造工坊路线等级域,见下行计划登记) |
| `vision_used` | 真视类工具累计使用次数终身计数(迭代 22 新增——信标网络解锁行为条件;**v6 域后扩列补零、不升版本**;写入唯一经真视计数入口,冒险收束聚合入账;断点随件键=`vision_counter_used`,续玩回填缺省 0) | [秘宝](../跨局循环/秘宝.md) |
| ~~`forge_levels`~~ | ~~属性锻造三档等级~~ | **已退役**(迭代 21 T5:域与访问器随攻防所有权切分删除;继任 = 锻造路线等级域) |
| `forge_routes`(**已落地 v7——锻造工坊实装批 T5,迭代 23**) | {levels: 六路线等级(sword/spear/staff/light/heavy/ward), unlocked: 直购解锁路线 id 清单, weapon: 当前武器路线 id, armor: 当前甲路线 id};无特效域;旧档缺键补齐 = 初始持有态 | [锻造工坊](../跨局循环/锻造工坊.md) |
| `breakpoint` | 冒险断点快照(停点粒度续玩域;空字典 = 无断点合法态——「继续上局」隐藏) | [冒险结构](../冒险循环/冒险结构.md) |
v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零迁移损失;收束自增域(通关次数/Boss 计数/终身计数/真视使用计数)的唯一入账点 = `apply_adventure_summary`(vision_used 走 `add_vision_used` 收束)。**v6 域后扩列不升版先例**:`vision_used`(迭代 22,2026-10-12——迭代 21 同款口径;断点随件 `vision_counter_used` 同批新增;旧断点随件键 `bonus_aux_carry` 已随替换子交互拆除退役,迭代 22 T5)。
v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零迁移损失;收束自增域(通关次数/Boss 计数/终身计数/真视使用计数)的唯一入账点 = 冒险摘要入账(vision_used 走真视计数入口收束)。**v6 域后扩列不升版先例**:`vision_used`(迭代 22,2026-10-12——迭代 21 同款口径;断点随件 `vision_counter_used` 同批新增;旧断点随件键 `bonus_aux_carry` 已随替换子交互拆除退役,迭代 22 T5)。
消费面纪律:其他系统只经 SaveManager 的类型化访问器(读余额/加钥石/花钥石、读写树等级等)与域更新原语(`apply_adventure_summary`)操作存档,不碰内部数据结构。三条槽位纪律(迭代 33 卡①):一、无激活态下一切写盘路径拒绝并返回 false(boot 后未进档/未新档即结算或购买=不落盘,激活后照常);二、槽文件名单点派生——消费方禁各自拼槽文件名,测试与探针的隔离走注入口(`attach_slot_template`/`peek_summary_at`);三、UI 读别槽摘要只走窥视 API `peek_slot_summary(slot)`(不激活、不写内存、零盘面副作用),返回结构 `{"readable", "has_file", "keystone", "cleared", "total"}`——`readable`=槽文件可读(空槽=零值合法态可读;损坏/高版本/缺版本字段=不可读标记),`has_file`=槽主文件在盘与否(空槽/有档的操作集二分判定来处;非摘要第四项、零 UI 语义),三项摘要值(`keystone`/`cleared`/`total`)只在 `readable=true` 时有意义。
消费面纪律:其他系统只经存档系统的类型化访问器(读余额/加钥石/花钥石、读写树等级等)与域更新原语(冒险摘要入账)操作存档,不碰内部数据结构。三条槽位纪律(迭代 33 卡①):一、无激活态下一切写盘路径拒绝并返回 false(boot 后未进档/未新档即结算或购买=不落盘,激活后照常);二、槽文件名单点派生——消费方禁各自拼槽文件名,测试与探针的隔离走注入口(槽模板注入/摘要窥视);三、UI 读别槽摘要只走窥视 API(不激活、不写内存、零盘面副作用),返回结构 = 可读标记 + 三项摘要值(钥石/通关数/总局数)——可读标记 = 槽文件可读(空槽=零值合法态可读;损坏/高版本/缺版本字段=不可读标记);槽主文件在盘与否不作摘要项、只供操作集二分判定(零 UI 语义),三项摘要值只在可读时有意义。
### 读写时机
```
启动 ──▶ 不读盘(boot 无激活态,不读写任何槽;只做一次旧单档迁移,见「版本与迁移」节)
选档界面「新档」/「进档」──▶ create_slot / activate_slot(读盘=该槽文件,建立激活态;迭代 33 卡①/卡③)
正式冒险收束(adventure_finished)──▶ apply_adventure_summary 入账 ──▶ 原子写盘(落当前激活槽)
选档界面「新档」/「进档」──▶ 建立激活态(读盘=该槽文件;迭代 33 卡①/卡③)
正式冒险收束 ──▶ 冒险摘要入账 ──▶ 原子写盘(落当前激活槽)
营地钥石消费(元进度实现批接线)──▶ 每笔变更后即时写盘(落当前激活槽)
冒险停点(choices_presented 选路停点)──▶ 断点快照写盘(迭代 8 卡①)
冒险停点(选路停点)──▶ 断点快照写盘(迭代 8 卡①)
冒险三结局收束(冒险成功/失败/放弃)──▶ 清除断点 + 原子写盘(迭代 8 卡①/T3 退出口径)
```
@@ -124,13 +124,13 @@ v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零
断点写盘纪律(迭代 8 卡①):停点快照在每个选路停点(战斗胜利/兑付确认/入口呈现后的选路态)整体覆写——战斗进行中不写(棋盘中局不入档,续玩恢复至最近停点、当场战斗从头打)。因为快照总在每个停点即时落盘,「关游戏走人」在任何停点都不丢续玩进度,无需窗口关闭钩子。
写盘是原子操作:先写临时文件(`<路径>.tmp`),写完改名覆盖正式档——写一半断电不留烂档。
写盘是原子操作:先写同目录临时文件,写完改名覆盖正式档——写一半断电不留烂档。
单局测试入口不写档:调试两层与 headless 单关测试路径(LevelRunner.start_level,仅 QA 工具使用)不接写盘——测试模式不污染生涯数据;只有正式冒险(AdventureRunner)收束才入账。
单局测试入口不写档:调试两层与 headless 单关测试路径(仅 QA 工具使用)不接写盘——测试模式不污染生涯数据;只有正式冒险收束才入账。
### 版本与迁移
存档带版本字段(`version`,当前 = **7**;版本沿革:v2 = 断点域出生〔迭代 8 卡①〕→ v3 = 断点附随件扩列 + 摘要 keystone_drops〔迭代 10 奖励重构〕→ v4 = 同调批扩域〔boss_kill_count / attunement_equipped / attune_refresh_level,迭代 11 第①批〕→ v5 = 天赋树域〔tree_nodes / fork_sides,迭代 12〕→ v6 = 生涯终身计数域〔adventure_total / adventure_cleared,迭代 18 卡④〕→ **v7 = 锻造工坊域〔forge_routes,迭代 23 T5——已落地:{levels 六路线等级 + unlocked 直购解锁清单 + weapon/armor 当前选中标记},无特效域;旧档缺键由 _normalize 按默认补齐 = 初始持有态(sword/light 在档、余四锁、选中=初始路线),v6→v7 空迁移 = 缺键补齐模式(v3→v4 先例);开发阶段禁迁移禁折算——契约 4〕**;与 `game/core/config_db.gd` 的 SCHEMA_VERSION 同源)。三条走向:
存档带版本字段(`version`,当前 = **7**;版本沿革:v2 = 断点域出生〔迭代 8 卡①〕→ v3 = 断点附随件扩列 + 摘要 keystone_drops〔迭代 10 奖励重构〕→ v4 = 同调批扩域〔boss_kill_count / attunement_equipped / attune_refresh_level,迭代 11 第①批〕→ v5 = 天赋树域〔tree_nodes / fork_sides,迭代 12〕→ v6 = 生涯终身计数域〔adventure_total / adventure_cleared,迭代 18 卡④〕→ **v7 = 锻造工坊域〔forge_routes,迭代 23 T5——已落地:{levels 六路线等级 + unlocked 直购解锁清单 + weapon/armor 当前选中标记},无特效域;旧档缺键按默认补齐 = 初始持有态(sword/light 在档、余四锁、选中=初始路线),v6→v7 空迁移 = 缺键补齐模式(v3→v4 先例);开发阶段禁迁移禁折算——契约 4〕**)。三条走向:
| 情形 | 处置 |
|---|---|
@@ -138,13 +138,13 @@ v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零
| 版本更高(未来版本的档) | **拒读且禁写**:不加载、后续写盘一律拒绝、内存以默认新档运行——保护未来版本的档不被当前版本覆写 |
| 版本更低(旧档) | 读档时逐级迁移:从旧版本起逐个调用注册的迁移函数升到当前版本;任一步失败按损坏兜底处理 |
迁移函数经 `register_migration(旧版本号, 函数)` 注册:函数把数据从「旧版本」改写成「旧版本 + 1」。生产迁移链现行六件(v2 起自注册):v1→v2 = 断点域补空;v2→v3 = 断点附随件与摘要 keystone_drops 补键(读取方 .get 兜底);v3→v4 = 同调三域补键;v4→v5 = 树域两键补齐(原挂接的「三体系迁移」体已随迭代 21 T5 攻防所有权切分删除——`save_manager.gd:96-98`、`meta_progression.gd:294-295` 实证;forge_levels 旧域同批清退,加载时按默认模型键集白名单收窄,`save_manager.gd:399-401`);v5→v6 = 生涯两终身计数域补零;**v6→v7 = 锻造工坊域 forge_routes 缺键补齐(迭代 23 T5——无结构迁移体,版本号推进仅为 schema_version 对齐;解锁态派生不落档:初始持有〔表 initial_owned〕∨ 直购〔unlocked 清单〕∨ 练满前一档〔levels[prereq]≥cap〕)**。任一步失败按损坏兜底处理。
迁移函数按旧版本号逐一注册:函数把数据从「旧版本」改写成「旧版本 + 1」。生产迁移链现行六件(v2 起自注册):v1→v2 = 断点域补空;v2→v3 = 断点附随件与摘要 keystone_drops 补键(读取方 .get 兜底);v3→v4 = 同调三域补键;v4→v5 = 树域两键补齐(原挂接的「三体系迁移」体已随迭代 21 T5 攻防所有权切分删除;forge_levels 旧域同批清退,加载时按默认模型键集白名单收窄);v5→v6 = 生涯两终身计数域补零;**v6→v7 = 锻造工坊域 forge_routes 缺键补齐(迭代 23 T5——无结构迁移体,版本号推进仅随表结构变化;解锁态派生不落档:初始持有〔表 initial_owned〕∨ 直购〔unlocked 清单〕∨ 练满前一档〔levels[prereq]≥cap〕)**。任一步失败按损坏兜底处理。
旧单档一次性迁移(迭代 33 卡①,用户裁决 Q1 甲):本系统从单档制换代为 3 存档位制时,盘上残留的旧单档 `user://save.json` 在启动时被处理——文件**字节级原样搬运**进槽 1(`user://save_slot_1.json`,零解析零改造:损坏或高版本的旧档也原样迁入,之后由该槽进档时的损坏兜底/高版本拒读接管,不静默丢数据),源文件改名 `user://save.json.migrated.bak` 留痕不删;槽 1 文件已存在则不重迁(幂等,二次启动零操作);迁移失败(拷贝或改名失败)源文件保留、下次启动重试。留痕与结果记在启动日志(开发者向,非玩家面)。
旧单档一次性迁移(迭代 33 卡①,用户裁决 Q1 甲):本系统从单档制换代为 3 存档位制时,盘上残留的旧单档文件在启动时被处理——文件**字节级原样搬运**进槽 1(零解析零改造:损坏或高版本的旧档也原样迁入,之后由该槽进档时的损坏兜底/高版本拒读接管,不静默丢数据),源文件改名留痕不删;槽 1 文件已存在则不重迁(幂等,二次启动零操作);迁移失败(拷贝或改名失败)源文件保留、下次启动重试。留痕与结果记在启动日志(开发者向,非玩家面)。
### 损坏兜底
档案读不开(文件损坏、JSON 解析失败、结构出域、缺版本字段)时:原文件改名为 `<路径>.corrupt.bak`(已有同名备份则覆盖)留下现场,游戏用默认新档继续运行,并留下警告日志。两条底线:不静默丢档(坏档永远可人工找回)、不崩溃(坏档不挡玩)。
档案读不开(文件损坏、JSON 解析失败、结构出域、缺版本字段)时:原文件改名留一份坏档备份(已有同名备份则覆盖)留下现场,游戏用默认新档继续运行,并留下警告日志。两条底线:不静默丢档(坏档永远可人工找回)、不崩溃(坏档不挡玩)。
## 依赖与输出
@@ -164,12 +164,12 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)把单
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| ~~P1~~ | ~~快照与复盘的持久化承接~~ | **已兑现**(2026-09-29 迭代 8 卡①:断点域承载 + rng「主种子+计数器」快照契约回填面 `restore_adventure`) |
| ~~P1~~ | ~~快照与复盘的持久化承接~~ | **已兑现**(2026-09-29 迭代 8 卡①:断点域承载 + rng「主种子+计数器」快照契约回填面) |
| ~~P2~~ | ~~多存档槽位~~ | **已实现**(2026-10-22 迭代 33 卡①——3 存档位三文件制落地,口径见「三存档位」段;原「单档 MVP(2026-09-21 裁决)」后置条随兑现注销) |
## 调参表
本系统没有玩法调参数值。两个结构常量:存档版本号(=6,与 `game/core/config_db.gd` 的 SCHEMA_VERSION 同源)、最近摘要保留条数(10)。二者是结构契约不是平衡值,改动属于版本迁移事件,不走调参批。
本系统没有玩法调参数值。两个结构常量:存档版本号(当前 7)、最近摘要保留条数(10)。二者是结构契约不是平衡值,改动属于版本迁移事件,不走调参批。
## 验收标准
@@ -179,7 +179,7 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)把单
- 改版不弃档:旧版本档经迁移链读回;低版本档缺新域按默认补齐,既有值保留(冒烟承载)。
- 断点域契约(迭代 8 卡①):七字段拍板面写读往返逐位一致;旧档无断点域迁移为合法空态;三结局清除(冒烟+单测承载)。
- 域界干净:meta 永久进度域不含局内系统资源键(行动点/储备行动点/道具库存/生命/GP)——断点域按豁免条款单列(冒烟承载,2026-09-29 卡①修订)。
- 零玩法依赖:SaveManager 不引入任何玩法脚本、不碰数值表与随机服务,可独立实例化验证(冒烟独立段承载)。
- 零玩法依赖:存档系统不引入任何玩法脚本、不碰数值表与随机服务,可独立实例化验证(冒烟独立段承载)。
## 待办
@@ -1,7 +1,7 @@
# 数据配置
> 一句话:数值表与单一事实源管线
> 状态:已定稿(自旧体系迁移);现行为 v1.0
> 状态:已定稿(自旧体系迁移);现行为 v1.1
## 设计目的
@@ -18,7 +18,7 @@
| 负责 | 说明 |
|---|---|
| 全部数值表的结构与数据 | 表结构类(声明每列的类型、取值域与默认值)与数据文件成对交付;结构先行,数值由各回填责任方回填 |
| 全局读取口与随机服务 | 两个进程级单例:类型化读取口(代码标识符 ConfigDB)与统一随机流服务(代码标识符 RngService) |
| 全局读取口与随机服务 | 两个进程级单例:类型化读取口与统一随机流服务 |
| 加载、校验与热重载管线 | 启动加载、fail-fast 校验、调试构建热重载的状态管理 |
| 进度坐标查行 | 进度键表(跟着冒险进度成长的表)按(关卡序号 × 节点深度)两维坐标解析取行 |
| 表清单与对账 | 表路径固定、清单封闭;表实与 schema 声明逐字段对账 |
@@ -28,7 +28,7 @@
| 不负责 | 归谁 |
|---|---|
| 各表数值定值与玩法公式 | 各回填责任方(清单见核心规则的表清单),平衡总归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| 存档格式与版本比较逻辑 | [存档序列化](存档序列化.md),本系统只出版本常量与随机快照契约 |
| 存档格式与版本比较逻辑 | [存档序列化](存档序列化.md),本系统只出随机快照契约 |
| 界面呈现 | [HUD 框架](HUD框架.md) |
| 随机用途的设计合理性 | 消费方系统自证(随机须有界、可见或可推理),新增随机走扩签通道 |
@@ -52,24 +52,25 @@
一句话总结这条规则:改数值 = 改表回填,不动代码;表不变,行为逐位相同。仅调试构建存在两个额外通道:从命令行注入的根目录加载测试夹具表;调试覆盖值可临时改读数、不回写表文件,正式构建结构性不存在覆盖源。
现行表集合如下(表结构类名供代码标识与工具链检索):
现行表集合如下:
| 表 | 载什么 | 回填责任方 |
|---|---|---|
| 行动经济表 `ActionEconomyTable` | 每轮行动点、动作价目、停手与刷新门槛 | [行动经济](../排雷循环/行动经济.md) |
| 棋盘生成参数表 `BoardGenTable` | 板尺寸、雷密度、可推理率目标等(进度键表) | [棋盘生成](../排雷循环/棋盘生成.md) |
| 变异表 `VariationTable` | 变异类型清单(现行六行:region 六行两轴×三关〔2026-09-20 落地〕;首发 global 整图型贫矿脉 2026-10-16 用户令屏蔽删行,global 型机制代码保留) | [地形变异](../排雷循环/地形变异.md) |
| 敌人表 `EnemyTable` | 敌种基线数值、遭遇数量区间、护甲类型、技能池模板(进度键表) | [敌人系统](../战斗循环/敌人系统.md) |
| 技能表 `SkillTable` | 技能行定义(类型/倍率/冷却/增益引用/触发条件)与保底阈值 | [敌人系统](../战斗循环/敌人系统.md) |
| 主角表 `HeroTable` | 主角生命上限、基础攻击与护甲等级(armor_class)等单行战斗数值 | [敌人系统](../战斗循环/敌人系统.md) |
| 状态效果表 `StatusEffectTable` | 增益/减益效果定义行 | [增益状态](../战斗循环/增益状态.md) |
| 奖励表 `RewardTable` | 奖励条目、类别、权重与档位 | [地图战斗资源](../战斗循环/地图战斗资源.md) |
| 结算表 `SettlementTable` | 结算段序、当量参考敞口、封顶与克制矩阵(单行全局) | [战斗结算](../战斗循环/战斗结算.md) |
| 秘宝表 `TreasureItemTable`(`treasure_items.tres`) | 秘宝定义行 | [秘宝](../跨局循环/秘宝.md) |
| 难度表 `DifficultyTable` | 难度修饰乘数(占位空表) | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| 关卡表 `LevelTable` | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) |
| 冒险表 `AdventureTable` | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) |
| 元进度表 `MetaTable` | 元进度字段(已建表 2026-09-21——现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族,顺带扩列) | [元进度](../跨局循环/元进度.md) |
| 行动经济表 | 每轮行动点、动作价目、停手与刷新门槛 | [行动经济](../排雷循环/行动经济.md) |
| 棋盘生成参数表 | 板尺寸、雷密度、可推理率目标等(进度键表) | [棋盘生成](../排雷循环/棋盘生成.md) |
| 变异表 | 变异类型清单(现行六行:region 六行两轴×三关〔2026-09-20 落地〕;首发 global 整图型贫矿脉 2026-10-16 用户令屏蔽删行,global 型机制代码保留) | [地形变异](../排雷循环/地形变异.md) |
| 敌人表 | 敌种基线数值、遭遇数量区间、护甲类型、技能池模板(进度键表) | [敌人系统](../战斗循环/敌人系统.md) |
| 技能表 | 技能行定义(类型/倍率/冷却/增益引用/触发条件)与保底阈值 | [敌人系统](../战斗循环/敌人系统.md) |
| 主角表 | 主角生命上限、基础攻击与护甲等级(armor_class)等单行战斗数值 | [敌人系统](../战斗循环/敌人系统.md) |
| 状态效果表 | 增益/减益效果定义行 | [增益状态](../战斗循环/增益状态.md) |
| 奖励表 | 奖励条目、类别、权重与档位 | [地图战斗资源](../战斗循环/地图战斗资源.md) |
| 结算表 | 结算段序、当量参考敞口、封顶与克制矩阵(单行全局) | [战斗结算](../战斗循环/战斗结算.md) |
| 秘宝表(`treasure_items.tres`) | 秘宝定义行 | [秘宝](../跨局循环/秘宝.md) |
| 难度表 | 难度修饰乘数(占位空表) | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| 关卡表 | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) |
| 冒险表 | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) |
| 元进度表 | 元进度字段(已建表 2026-09-21——现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族,顺带扩列) | [元进度](../跨局循环/元进度.md) |
| 战斗屏布局表(`battle_layout.tres`) | 分区几何与明细带两态高(单行;呈现层布局配置,非玩法数值——供呈现层与样张工具同源读取) | [战斗屏规格](../../ux/battle.md)(结构)/[HUD框架](HUD框架.md)(呈现常量) |
举证:本节为数据纪律约定,无计算。违规检测 = 静态扫描加人工复核,随各表回填定稿与里程碑检查执行。
@@ -88,11 +89,11 @@
取数有两个入口。玩法代码用取号入口:每调用一次,全局计数器加一并派生新种子,同一事件内共享同一实例。测试与预览用纯函数入口:按指定全局序号计算种子、不推计数器——预览不污染正式序列。种子由登记的派生公式计算(单盐),纯函数、跨引擎版本恒定,并登记了可复算的黄金向量。
全事件日志(并流批新增):冒险开始即开日志会话(user://logs/rng_log_<主种子>.jsonl),每次取数落一行——全局序号、用途标签、派生种子。号位在全序账本下不再单独回答"第 N 次某类事件"(路线依赖为已裁决代价),单点定位与复盘由日志承载。写盘失败只告警降级,不致命、不影响玩法随机。
全事件日志(并流批新增):冒险开始即开日志会话(随机事件日志文件,每主种子一份),每次取数落一行——全局序号、用途标签、派生种子。号位在全序账本下不再单独回答"第 N 次某类事件"(路线依赖为已裁决代价),单点定位与复盘由日志承载。写盘失败只告警降级,不致命、不影响玩法随机。
生命周期:进程启动后流待命(未注入主种子就取数,致命中止,禁止默认种子兜底)→ 冒险开始注入主种子并开日志会话 → 冒险中按事件取号(全局计数器只增不重置)→ 冒险结束流保留供复盘。续玩与复盘的快照 = 主种子加全局取数计数器,由[存档序列化](存档序列化.md)消费;快照切出须发生在没有在用取号实例的时刻。要新增随机用途(含战斗内随机),走扩签通道:改代码加用途标签并通过评审——词表封闭是刻意的设卡。
举证:派生公式与实现规约见 `game/core/rng_service.gd`(盐与派生常量为代码事实),黄金向量断言见 `game/tests/unit/data_config/rng_stream_boot_test.gd`;本文不转载。
举证:派生公式(单盐)由随机服务持有,本文不转载;黄金向量已登记可复算,逐向量复算相等即验收判据。
### fail-fast 校验:宁可启动失败,不带病运行
@@ -110,7 +111,7 @@
热重载(不重启进程、当场重新加载表文件的调试手段,仅调试构建)单独一套处置:战斗进行中的重载请求被闸门拒绝(闸门由[战斗结算](../战斗循环/战斗结算.md)在战斗开始时置位、结束复位),重载校验失败时保留旧表继续运行并给出错误清单——不杀调试会话,这是全部规则里唯一的软着陆。调试构建之外,加载与校验在任何构建形态一律致命。随机服务的状态线独立于表加载:重载成败都不触碰主种子与计数器。
举证:本节为处置纪律约定,无计算。校验双通道(正式通道致命、测试通道收集供冒烟逐类断言)与夹具注入为实现参照,见 `game/core/config_db.gd` 实现。
举证:本节为处置纪律约定,无计算。校验双通道与夹具注入为实现参照,本文不转载。
### 悬空引用校验:跨表引用指到的键必须存在
@@ -155,7 +156,7 @@
| 下游 | [冒险结构](../冒险循环/冒险结构.md) | 关卡表与冒险表承载(登记现状见「待办」);主种子注入入口 |
| 下游 | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | 难度表承载(占位);进度坐标查行的消费方 |
| 下游 | [元进度](../跨局循环/元进度.md) | 元进度表占位承载 |
| 下游 | [存档序列化](存档序列化.md) | 表结构版本常量;随机快照契约(主种子 + 全局取数计数器) |
| 下游 | [存档序列化](存档序列化.md) | 随机快照契约(主种子 + 全局取数计数器) |
| 下游 | [HUD 框架](HUD框架.md) | 调试热重载完成通知(仅调试构建) |
| 外发 | 无订阅 | 本系统不订阅任何玩法事件 |
@@ -186,7 +187,6 @@
| 参数 | 现行值 | 说明 |
|---|---|---|
| 表路径清单 | data/tables/ 固定(调试注入根除外) | 冻结:散装路径破坏单点访问 |
| 表结构版本常量 `SCHEMA_VERSION` | 当前 = 2(v2=断点域出生,迭代 8 卡①,与存档 SAVE_FORMAT_VERSION 同源) | 全部表结构携带版本字段;高于当前支持的版本拒读,比较逻辑归[存档序列化](存档序列化.md) |
| 随机流口径 | 统一流单盐单计数器 + 七用途标签封闭词表 | 统一口径为 2026-09-27 裁决;扩签 = 改代码 + 评审,有意设卡 |
| 进度坐标解析策略 | 可取范围内向上取整 + 警告(保证偏难);超出最大行封顶取最大值 + 警告;域越界致命(2026-09-18 两段拍板,原「就近向下」退役) | 冻结:静默降难会掩盖难度断档 |
| 入口锚行校验 | 写死开 | 非调参项;重议走文档修订流程 |
@@ -200,12 +200,11 @@
- 黄金向量:随机派生纯函数对登记向量逐个复算相等;同输入恒同输出;同序号重复调用恒等。
- 计数器纪律:连续取号跨模拟关卡切换,计数器单调不重置;热重载成败都不改变主种子与计数器。
- 向上取整解析:请求无精确行时返回**不低于**请求坐标的最近行并留一条警告;请求超出最大已有行时封顶取最大行并留警告;同类警告去重;精确命中零警告。
- 越域与未播种:坐标低于合法域(关卡序号 <1、节点深度 <0)在进入解析前致命中止(Fatal 级诊断 + 进程退出码 1,致命类单测 `tests/unit/data_config/config_db_fatal_oob_test.gd` 验证——迭代 19 卡① T4 探针退役顺带改指,原切片探针断言面与 run_all 致命类单测重复故退役);高于最大已有行走封顶取最大行(警告留痕,不致命);未设主种子就取随机数致命中止,无默认种子兜底。
- 越域与未播种:坐标低于合法域(关卡序号 <1、节点深度 <0)在进入解析前致命中止(Fatal 级诊断 + 进程非 0 退出,无兜底);高于最大已有行走封顶取最大行(警告留痕,不致命);未设主种子就取随机数致命中止,无默认种子兜底。
- 热重载门:战斗中重载被拒绝且读数不变、不发通知;门未置位时新值可读且通知恰一次;校验失败保留旧表停留就绪态。
- 版本常量在位:表结构版本常量存在且全部表结构携带版本字段(高版本拒读判定归存档序列化)。
- 零字面量、零第二路径:静态扫描加人工复核确认玩法代码无数值字面量、无读取口之外的表加载、无全局随机函数调用。
- 零界面依赖:本系统代码不引用任何界面类,无窗口实跑通过(静态扫描与实跑两项合成判定)。
- 表实一致:各表结构与已锁字段和 `data/schemas/` 对应声明逐字段一致。
- 表实一致:各表结构与已锁字段和表结构声明逐字段一致。
## 待办
@@ -1,7 +1,7 @@
# 设置与本地化
> 一句话:设置项与多语言
> 状态:范围草案·最小设置面已兑现(2026-09-29 迭代 8 卡④)
> 状态:范围草案·最小设置面已兑现(2026-09-29 迭代 8 卡④);现行为 v1.1
## 设计目的
@@ -92,7 +92,7 @@ MVP 不要求本系统的闭环:[不做清单](../../05-不做清单.md)把设
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| ~~P1~~ | ~~已承诺设置项落位:减动效开关、音量控制入口~~ | **已兑现**(2026-09-29 迭代 8 卡④最小设置面:音量 master bus 实时+持久、减动效开关转正〔测试调整板旧开关退役,设置面唯一入口〕、全屏切换、退出游戏;载体 presentation/settings_store.gd 独立存储 + views/settings_overlay.gd 视图;键位映射/多语言仍按后置表排期) |
| ~~P1~~ | ~~已承诺设置项落位:减动效开关、音量控制入口~~ | **已兑现**(2026-09-29 迭代 8 卡④最小设置面:音量 master bus 实时+持久、减动效开关转正〔测试调整板旧开关退役,设置面唯一入口〕、全屏切换、退出游戏;载体=独立设置存储 + 设置界面视图(一安装一份,不进存档);键位映射/多语言仍按后置表排期) |
| P1 | 多语言生产流程与首批语言清单 | 生产流程未定:文案怎么管理、谁负责校对 |
| P2 | 设置项全集(画面 / 键位 / 可访问性 / 语言各类别) | 全集无来源,待设计问题清单裁决后展开 |
| P2 | 结算风味词输入的承接 | 归档只登记了存在,各归各的时机 |
+10 -10
View File
@@ -1,7 +1,7 @@
# 元进度
> 一句话:跨局成长——钥石在营地换成永久强化,存档随冒险变厚
> 状态:**已定稿(现行为 v1.25)**——2026-09-18 元进度定稿批用户拍板;历次升版明细见 git 提交史。
> 状态:**已定稿(现行为 v1.26)**——2026-09-18 元进度定稿批用户拍板;历次升版明细见 git 提交史。
## 设计目的
@@ -62,18 +62,18 @@
| 功能界面 | 内容 | 解锁渠道 |
|---|---|---|
| 属性锻造 | 生命上限、基础攻击、护甲等级——三档可重复升级的属性强化 | 钥石直购 |
| 秘宝匣 | 秘宝解锁与同调装配——**件的目录与效果权威定义在[秘宝](秘宝.md)「目录(重设计 v1)」**(12 件三档〔2026-10-11 裁回能器删/换装券撤/第四只手删〕;旧目录全退役只作素材库) | 解锁=混合制(基础件钥石购买/特定件条件解锁——见秘宝.md「道具从哪来」;装配只在冒险开始前配置,冒险中零更换途径〔2026-10-11 终裁〕);**迭代 22 实装批落地(2026-10-12)**:购买钮(余额不足禁用态)+条件解锁进度呈现+分档装配呈现(名后缀·档位/未解锁禁装)+空目录合法态;界面数据投影=`treasure_shop_status`(解锁态/条件进度/价格单点);旧道具工坊渠道面(行族/词表/解锁函数)已顺带退役;钥石定价与阈值 provisional 归调参批 |
| 秘宝匣 | 秘宝解锁与同调装配——**件的目录与效果权威定义在[秘宝](秘宝.md)「目录(重设计 v1)」**(12 件三档〔2026-10-11 裁回能器删/换装券撤/第四只手删〕;旧目录全退役只作素材库) | 解锁=混合制(基础件钥石购买/特定件条件解锁——见秘宝.md「道具从哪来」;装配只在冒险开始前配置,冒险中零更换途径〔2026-10-11 终裁〕);**迭代 22 实装批落地(2026-10-12)**:购买钮(余额不足禁用态)+条件解锁进度呈现+分档装配呈现(名后缀·档位/未解锁禁装)+空目录合法态;界面数据投影=秘宝匣柜台状态(解锁态/条件进度/价格单点);旧道具工坊渠道面(行族/词表/解锁函数)已顺带退役;钥石定价与阈值 provisional 归调参批 |
| 奖励格图鉴 | 特殊奖励格的解锁——更强效果的格面 | 结构位现定;格子内容排 Alpha,随[地图战斗资源](../战斗循环/地图战斗资源.md)差异化批落地 |
属性锻造的树形节点结构已随迭代 12 落地在役——**结构/节点/数值的现行事实源 = [天赋树](天赋树.md),本文不复制树侧内容、只作指路**;本界面三档直线强化与树淬炼 flat 在注入面叠加生效(注入口径见下文「跨局注入」节)。
属性锻造的攻击、护甲两档已按[锻造工坊](锻造工坊.md) v0.6(成文时名「武器锻造」,2026-10-11 更名)设计定案转为武器与护甲的独立等级路线——**行族本体随迭代 21 T5 退役、路线为新建行族,已随迭代 23 实装入库(2026-10-12,新表 `forge.tres`)**。营地钥石入口族换代(2026-10-13 v1.18 迭代 23 反哺写入):buy_forge 随 forge_products 退役删除后,本柜台钥石消费写口 = **forge_buy_level(逐级锻造价)/ forge_buy_unlock(一次性解锁价)/ forge_select_weapon·armor(免费选中)**——写口唯一经 MetaProgression 锻造写口族(契约 9 写口单点,UI 只发意图);钥石定价历史考古参数 20 钥石/150% 公比见 `meta.tres:82-84` 退役批注,provisional 投值终值归 M4。
属性锻造的攻击、护甲两档已按[锻造工坊](锻造工坊.md) v0.6(成文时名「武器锻造」,2026-10-11 更名)设计定案转为武器与护甲的独立等级路线——**行族本体随迭代 21 T5 退役、路线为新建行族,已随迭代 23 实装入库(2026-10-12,新表 `forge.tres`)**。营地钥石入口族换代(2026-10-13 v1.18 迭代 23 反哺写入):原逐级购买写口随 forge_products 退役删除后,本柜台钥石消费写口 = **逐级锻造价 / 一次性解锁价 / 免费选中武器·甲**——写口唯一经元进度锻造写口族(契约 9 写口单点,UI 只发意图);钥石定价历史考古参数 20 钥石/150% 公比见 `meta.tres` 退役批注,provisional 投值终值归 M4。
**营地统一(2026-10-11 天赋树设计会话裁案甲;2026-10-13 v1.20 迭代 21 反哺写入——「落地前维持现役」过渡态终结)**:树=营地本体,本属性锻造柜台已随迭代 21 退役、属性升级唯一入口=树脊柱淬炼节点(四柜台并排已实装,2026-10-12 入库)。**完整模型(四柜台并排/钥匙工作间/图鉴常开)唯一事实源 = [天赋树](天赋树.md)「营地=树:统一模型」节——本文只登记本柜台在系统内的去留,不复制树侧内容。**
**投值语言锁定(2026-09-18 数值语言拍板)**:营地属性强化一律 **flat 整数加减**("每级 +1 基础攻击"式,而非"+50% 攻击"式)——避开百分比词条被数值成长稀释的先例陷阱、守住数字可读性锚;具体每级数值仍归元进度设计批投值。
各界面定价纪律:**定价曲线一律几何递增**(每级费用按公比上浮——原则已拍板,起步价与公比数字归设计批配读数),与产出逻辑配套:深层关钥石产出多、后级强化贵,逼玩家往深处走。~~秘宝匣首版只挂通关次数渠道~~(历史排期裁决已消解:秘宝匣现役=钥石直购+条件解锁混合制,迭代 22 落地,2026-10-12)。**存档义务随柜台产生**:钥石余额、永久强化等级表、每关通关次数表、Boss 首杀标记位——四件已逐域登记在 registry `save_file` 条目(内容 owner = 元进度,承载 owner = [存档序列化](../表现与支撑/存档序列化.md))。
各界面定价纪律:**定价曲线一律几何递增**(每级费用按公比上浮——原则已拍板,起步价与公比数字归设计批配读数),与产出逻辑配套:深层关钥石产出多、后级强化贵,逼玩家往深处走。~~秘宝匣首版只挂通关次数渠道~~(历史排期裁决已消解:秘宝匣现役=钥石直购+条件解锁混合制,迭代 22 落地,2026-10-12)。**存档义务随柜台产生**:钥石余额、永久强化等级表、每关通关次数表、Boss 首杀标记位——四件已逐域登记在存档登记条目(内容 owner = 元进度,承载 owner = [存档序列化](../表现与支撑/存档序列化.md))。
护甲等级进首版属性柜台是 2026-09-18 拍板(用户选 A),其代价条款见下方换算率节的强条目登记。
@@ -81,7 +81,7 @@
解锁让玩家变强,难度验收区间随之需要平移——双方约定(难度曲线与Boss 定稿口径)永久强化的影响**不直接改难度判据**,而是按统一换算率折算成关卡完成率带的平移量后套用:完美档 gate 与 normal 目标带的判读口径不变,带随解锁等级平移。
换算率表(registry 键 `meta_progression_conversion_table`,数值行读 `data/tables/meta.tres` conversion 行族)登记三个属性维度(2026-09-18 拍板;"行" = 表内一条记录,一个属性维度即一行条目,全仓与"行值"同义)。现行表值(2026-09-23 换算率标定批投值——OFAT 冒险注入档与白板同种子配对的逐关条件通关率差实测,冒险条件口径 + 当日数值包现行态;报告=git 提交 7f24b64(已按生命周期条款退役),判定端 ④ 段可复判):
换算率表(数值行读 `data/tables/meta.tres` conversion 行族)登记三个属性维度(2026-09-18 拍板;"行" = 表内一条记录,一个属性维度即一行条目,全仓与"行值"同义)。现行表值(2026-09-23 换算率标定批投值——OFAT 冒险注入档与白板同种子配对的逐关条件通关率差实测,冒险条件口径 + 当日数值包现行态;报告已按生命周期条款退役,判定端 ④ 段可复判):
| 换算行 | 折算语义 | 每级带平移量(现行表值) |
|---|---|---|
@@ -89,17 +89,17 @@
| 基础攻击强化 | 每级折算为关卡完成率带平移量 | **9 点** |
| 护甲等级强化 | 每级折算为关卡完成率带平移量 | **1 点** |
判定端(tools/balance_verifier.py)表值驱动消费:验收带按解锁状态 flat÷每级flat×表值平移后套用,表改则判定随动。护甲行强条目重测义务已于标定批履行(解锁后参数实测,非基线外推);标定同时暴露并修复护甲断链(begin_battle `hero_armor_class` 参数零消费——主角护甲锻造与加算区护甲轴的生效值此前未进战斗),修复见 battle_orchestrator `_defender_ac` 批注。
判定端按表值驱动消费:验收带按解锁状态折算后套用,表改则判定随动。护甲行强条目重测义务已于标定批履行(解锁后参数实测,非基线外推);标定同时暴露并修复一处缺口:主角护甲锻造与局内加算区护甲轴的生效值此前未进战斗(该轴此前无消费方=已知缺口,已随标定批修复并登记)。
后续柜台商品(秘宝匣钥石价、奖励格图鉴)上架时逐条补行——每次上架新商品伴随一行换算登记,逐行归因纪律不破。
### 跨局注入:解锁怎么进下一局
注入通道沿用两处预留位(2026-09-18 拍板确认):[冒险结构](../冒险循环/冒险结构.md)的**冒险开始参数面**(无解锁时为空参数)+ registry `hero_override_parameter_passing`(begin_battle 显式入参,-1 = 表值兜底)。
注入通道沿用两处预留位(2026-09-18 拍板确认):[冒险结构](../冒险循环/冒险结构.md)的**冒险开始参数面**(无解锁时为空参数)+ 英雄覆盖参数传递通道(战斗初始化显式入参,-1 = 表值兜底)。
执行口径:开始一次新冒险时,本系统按存档内的永久强化状态算好一份 override,逐字段显式传给战斗初始化——配置因此不是完全静态的,随游戏进程动态改变(用户拍板原话口径)。字段清单随柜台扩展:道具池、遭遇权重、奖励格解锁是集合操作,参数面登记**追加 / 替换**两种语义。
**开局属性四层叠加(2026-10-12 v1.16 补登记——事实源=[锻造工坊](锻造工坊.md)「注入衔接」节 v0.6 / [天赋树](天赋树.md);原「属性锻造三档是数值覆盖」随迭代 21 T5 行族退役换代为下述分层;2026-10-13 v1.18 迭代 23 反哺写入补终态句)**:开局属性 = 角色基础值 + 树生命/命中淬炼 flat + 锻造路线加值(当前选中武器的攻击加值 + 当前甲的 AC 加值——默认值由锻造工坊配置常驻持有、与柜台解锁状态解耦,当前选中读存档域)+ 局内加算区 flat。入战 AC 三层可加 = 甲型基础 AC + 甲路线等级加值 + 局内加算区累加——**第一层「甲型基础 AC」= 选中甲路线行 `base_ac`(`forge.tres`,轻 10/重 12/魔法护甲 9 provisional),已随迭代 23 实装在役(`effective_ac` 三层公式第一层,树 flat + 锻造路线加值同批在役)**。
**开局属性四层叠加(2026-10-12 v1.16 补登记——事实源=[锻造工坊](锻造工坊.md)「注入衔接」节 v0.6 / [天赋树](天赋树.md);原「属性锻造三档是数值覆盖」随迭代 21 T5 行族退役换代为下述分层;2026-10-13 v1.18 迭代 23 反哺写入补终态句)**:开局属性 = 角色基础值 + 树生命/命中淬炼 flat + 锻造路线加值(当前选中武器的攻击加值 + 当前甲的 AC 加值——默认值由锻造工坊配置常驻持有、与柜台解锁状态解耦,当前选中读存档域)+ 局内加算区 flat。入战 AC 三层可加 = 甲型基础 AC + 甲路线等级加值 + 局内加算区累加——**第一层「甲型基础 AC」= 选中甲路线行 `base_ac`(`forge.tres`,轻 10/重 12/魔法护甲 9 provisional),已随迭代 23 实装在役(入战 AC 三层公式第一层,树 flat + 锻造路线加值同批在役)**。
### 解锁界面:双入口框架
@@ -132,7 +132,7 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)整体
| P1 | 营地功能界面内容铺量与定价数字 | 界面结构已定稿;清单明细、几何定价的起步价与公比、Boss 两档产出数值,随设计批配读数投值 |
| P1 | 秘宝匣钥石直购价 | **已投 provisional 占位**(普通档 10/优秀档 25 钥石,迭代 22 落地,2026-10-12);终值归调参批(原「首版只挂通关次数渠道」排期态随混合制落地消解) |
| P1 | 奖励格图鉴的格子内容 | 排 Alpha,随[地图战斗资源](../战斗循环/地图战斗资源.md)差异化批 |
| P1 | 换算率表数值行标定 | **已完成**(2026-09-23 换算率标定批——首版三行投值 6/9/1,护甲行强条目重测顺带履行并修复护甲断链)(原系笔误孤例,registry 与 meta.tres 均为 6/9/1——2026-09-30 勘误) |
| P1 | 换算率表数值行标定 | **已完成**(2026-09-23 换算率标定批——首版三行投值 6/9/1,护甲行强条目重测顺带履行并修复护甲断链)(原系笔误孤例,换算率登记与 meta.tres 均为 6/9/1——2026-09-30 勘误) |
| P2 | 营地界面与解锁呈现实现 | 归[HUD 框架](../表现与支撑/HUD框架.md)协作批,以表现层样张过审为前置 |
## 调参表
@@ -141,7 +141,7 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)整体
| 表 | 键结构归属 | 数值行 |
|---|---|---|
| 换算率表(`meta_progression_conversion_table`) | 本文 owner,键结构见 `data/schemas/meta_table.gd` | `data/tables/`,设计批投值(首版三行,护甲行强条目) |
| 换算率表 | 本文 owner,键结构见元进度表结构声明 | `data/tables/`,设计批投值(首版三行,护甲行强条目) |
| 定价表(属性锻造 / 秘宝匣 / 奖励格图鉴费用曲线) | 随设计批建表 | `data/tables/`,几何递增参数 |
| 钥石产出(Boss 首杀额 / 重复杀小额) | 入账挂点见[冒险结构](../冒险循环/冒险结构.md)调参表(boss_keystone 行);Boss 两档归本文设计批(每关入账值 keystone_per_level 已随迭代 10 奖励重构退役) | `data/tables/` |
+27 -29
View File
@@ -1,9 +1,9 @@
# 天赋树
> 一句话:营地本身——跨局成长的结构承载:钥石逐节点点亮一棵树,属性淬炼、工作间解锁、规则抉择与同调挂载全部组织在树上
> 状态:**v0.10 R1 机制位批(2026-10-13,用户推翻迭代 21 追认单 #1 裁撤案)**——「等级阈值前置」以机制位形态顺带重立:schema 新增 `requires_level: {node_id: n}`(前置节点等级 ≥ n 才可购;键悬空/阈值 <1/类型错加载即拒)+ 购买判定分支 + 行板门槛呈现(未达标 🔒 标+悬停门槛文案)+ 单测八断言;**现役 12 行零配置=机制位空转零行为影响**,配置归表行随需填。承 v0.9 迭代 21 反哺写入(2026-10-13,用户裁「按呈报写入文档」)——全文档换代为 12 节点落地实态:头部实装态 / 节点总表前言 / schema 连带 / 已建未接面(三条→两接一维持)/ P0 段 / 依赖表 / 验收实态七处换代;「设计避让」句按卡片 v1.7 ⑪②裁决改写为折扣定义边界口径。承 v0.8 Fork 一节点双态换代(2026-10-11 用户裁——开局姿态/危险直觉/财富之道三天赋=三节点、购买解锁后 A/B 双态可切,现役 A/B 双行结构收编、全树 15→12)
> 状态:**已定稿(现行为 v0.11)**——12 节点结构在役(脊柱=生命/命中一线一节点,Fork 三节点双态);「等级阈值前置」为机制位(前置节点等级 ≥ n 才可购;键悬空 / 阈值 <1 / 类型错加载即拒;未达标节点锁标 + 悬停门槛文案),现役 12 行零配置=零行为影响;全部数值行 provisional,归数值批。
> **📖 节点状态记录指引(读这份文档先看这里)**:本树全部 12 个节点=下方「核心规则 → 节点总表」,逐节点列名称/脉/类型/前置/效果方向;**全部数值行(价格 / 等级上限 / 每级效果取值 / 限次值)不在本文**,事实源 = 配置文件 `game/data/tables/meta_tree.tres`(provisional 归数值批)。要查"有哪些节点、怎么连"看本文总表,要查"多少钱、加多少"开配置文件。
> **📖 节点状态记录指引(读这份文档先看这里)**:本树全部 12 个节点=下方「核心规则 → 节点总表」,逐节点列名称/脉/类型/前置/效果方向;**全部数值行(价格 / 等级上限 / 每级效果取值 / 限次值)不在本文**,事实源 = 配置文件 `data/tables/meta_tree.tres`(provisional 归数值批)。要查"有哪些节点、怎么连"看本文总表,要查"多少钱、加多少"开配置文件。
## 设计目的
@@ -19,7 +19,7 @@
| 负责 | 说明 |
|---|---|
| 节点结构与解锁规则 | 12 节点的一脊三脉构成、脊柱行门槛(节点第 N 级)、点亮前置(requires)+ **等级阈值前置(requires_level——R1 机制位 2026-10-13 顺带重立,现役零配置空转)**、Fork 一节点双态与免费切换、彩蛋觉醒——结构事实源 = `data/tables/meta_tree.tres` + schema MetaTreeTable(**已实装在役,迭代 21 T1**) |
| 节点结构与解锁规则 | 12 节点的一脊三脉构成、脊柱行门槛(节点第 N 级)、点亮前置(requires)+ **等级阈值前置(requires_level——R1 机制位 2026-10-13 顺带重立,现役零配置空转)**、Fork 一节点双态与免费切换、彩蛋觉醒——结构事实源 = `data/tables/meta_tree.tres`(**已实装在役,迭代 21 T1**) |
| 树的购买与货币规则 | 全树钥石单币;free 节点零币;购买校验四件套(等级上限 / 行门槛 / 前置 / 未解锁) |
| 营地统一模型 | 树=营地本体(呈现=四柜台并排):生命/命中淬炼唯一入口=树脊柱淬炼节点,攻/AC 唯一入口=锻造工坊路线(所有权切分,2026-10-11 裁);工作间钥匙模型(锻造工坊 / 秘宝匣);图鉴常开;旧属性锻造柜台退役(生命归脊柱、攻防归锻造工坊) |
| 树域存档的内容语义 | tree_nodes(node_id→已购等级)与 fork_sides(fork_group→激活态)两域语义;旧档废弃规则(开发阶段无迁移——2026-10-11 用户裁) |
@@ -58,20 +58,20 @@
### 一脊三脉:节点构成
天赋树(玩家可见名;代码面 `meta_tree`)换代后 12 节点,按脉(vein)分四族:
天赋树(玩家可见名)换代后 12 节点,按脉(vein)分四族:
| 脉 | 节点数 | 内容 | 结构要点 |
|---|---|---|---|
| 主干脊柱(spine) | 2 | 生命淬炼 / 命中淬炼——一线一节点(kind=stat),每节点内 4 个等级状态 | 行门槛:节点第 N 级可购需全树已购总等级 ≥ 4×(N−1)。2026-10-11 一线一节点换代(用户裁:一个内容一个节点、等级在节点内、不拆多节点)——原「一线 4 层×层内 3 购」收编为单节点 4 级(层=级),SPINE_ROW_GATE=4 公式平移到等级轴,row 字段随层行退役;达成节奏重算=注记在册、值归数值批 |
| 主干脊柱(spine) | 2 | 生命淬炼 / 命中淬炼——一线一节点(kind=stat),每节点内 4 个等级状态 | 行门槛:节点第 N 级可购需全树已购总等级 ≥ 4×(N−1)。2026-10-11 一线一节点换代(用户裁:一个内容一个节点、等级在节点内、不拆多节点)——原「一线 4 层×层内 3 购」收编为单节点 4 级(层=级),行门槛常量=4 的公式平移到等级轴,row 字段随层行退役;达成节奏重算=注记在册、值归数值批 |
| 锻造脉(forge) | 2 | 锻造工坊(kind=unlock,free,工作间钥匙)+ 彩蛋节点地脉共鸣(kind=easter) | 锻造工坊前置 = **无**(free,直接可点亮——2026-10-11 攻防所有权切分裁,原「攻击淬炼累计 ≥2 且 护甲淬炼累计 ≥2」等级阈值前置随两线砍除作废);彩蛋挂本脉 |
| 钥石脉(keystone) | 2 | 秘宝匣(kind=unlock,free,工作间钥匙)/ 刷新升级(kind=rule) | 前置链:秘宝匣 ← 脊柱生命第 1 级;刷新升级 ← 秘宝匣 |
| 同调脉(attune) | 6 | 同调位 1/2/3 三节点(kind=socket)+ Fork 3 节点(kind=fork,一节点双态)= **3 同调位 + 3 双态开关节点** | **同调位 1 为根节点、无前置**,其后逐级前置(1→2→3);全部 Fork 前置 = 同调位 1;Fork=开局姿态/危险直觉/财富之道各一节点(2026-10-11 用户裁:一节点双态、解锁后 A/B 可切——非六节点) |
节点类型是封闭枚举六类:stat(生命/命中两线数值)/ rule(全局规则)/ unlock(内容解锁)/ socket(同调位)/ fork(一节点双态开关)/ easter(彩蛋觉醒)。字段、词表与跨字段约束由 schema 校验(MetaTreeTable.validate:脉 / 类型 / 币出域即拒、effects 键封闭、Fork 三组每组恰一节点、彩蛋 ≤3、禁 archetype 键——加载期校验)。**schema 连带已随迭代 21 T1 落地**:Fork 由 A/B 双行改单行双态(行持 a/b 双效果键 effects_a/b+name_a/b,效果按激活态取值;Fork 组词表 4→3 组)、规则键词表 2→6 键(见 Fork 节)、row 字段随一线一节点退役;「等级阈值前置」机制位 **2026-10-13 顺带重立**(requires_level 字段+加载校验,现役零配置——见头部状态行)。
节点类型是封闭枚举六类:stat(生命/命中两线数值)/ rule(全局规则)/ unlock(内容解锁)/ socket(同调位)/ fork(一节点双态开关)/ easter(彩蛋觉醒)。字段、词表与跨字段约束由 schema 校验(加载期校验:脉 / 类型 / 币出域即拒、effects 键封闭、Fork 三组每组恰一节点、彩蛋 ≤3、禁 archetype 键)。**schema 连带已随迭代 21 T1 落地**:Fork 由 A/B 双行改单行双态(行持 a/b 双效果键 effects_a/b+name_a/b,效果按激活态取值;Fork 组词表 4→3 组)、规则键词表 2→6 键(见 Fork 节)、row 字段随一线一节点退役;「等级阈值前置」机制位 **2026-10-13 顺带重立**(requires_level 字段+加载校验,现役零配置——见头部状态行)。
### 节点总表(12 节点)
换代后全名单(2026-10-11 Fork 一节点双态批换代:15→12)。数值行(价格/上限/效果取值)不在此表,读配置文件 `game/data/tables/meta_tree.tres`(**12 行结构已实装在役——迭代 21 T1,33 行旧结构已废弃重置不迁移**):
换代后全名单(2026-10-11 Fork 一节点双态批换代:15→12)。数值行(价格/上限/效果取值)不在此表,读配置文件 `data/tables/meta_tree.tres`(**12 行结构已实装在役——迭代 21 T1,33 行旧结构已废弃重置不迁移**):
| 节点 | 脉 | 类型 | 前置 | 效果方向 |
|---|---|---|---|---|
@@ -94,9 +94,9 @@
所有付费节点只收钥石(2026-10-07 用户裁甲落定;行内 currency 字段保留为呈现元数据)。free 节点零币:两枚工作间钥匙(锻造工坊 / 秘宝匣)与彩蛋(觉醒)不经购买消费。
购买校验四件套,全部通过才受理:①当前等级未满上限;②行门槛——脊柱节点第 N 级需全树已购总等级 ≥ 4×(N−1)(总等级口径 = tree_nodes 全部节点等级之和,免费点亮与分叉激活均计入——2026-10-11 歧义扫描裁甲沿现役实态;常量 SPINE_ROW_GATE=4;2026-10-11 一线一节点换代后门槛主语=节点目标等级,原层行判定作废;达成节奏重算归数值批);③requires 前置满足——点亮前置(前置节点全部已点亮)+ **等级阈值前置(requires_level:指定前置节点等级 ≥ n——R1 机制位 2026-10-13 顺带重立,2026-10-12 曾按卡片默认裁撤案执行、用户推翻补机制位;现役 12 行零配置空转,配置归表行随需填)**;④Fork 节点未解锁(一节点双态——解锁后的态切换走零币切换口,不经购买)。执行购买时另查钥石余额:余额不足则购买失败,存档零副作用(等级、余额、激活态均不动)。
购买校验四件套,全部通过才受理:①当前等级未满上限;②行门槛——脊柱节点第 N 级需全树已购总等级 ≥ 4×(N−1)(总等级口径 = tree_nodes 全部节点等级之和,免费点亮与分叉激活均计入——2026-10-11 歧义扫描裁甲沿现役实态;常量行门槛=4;2026-10-11 一线一节点换代后门槛主语=节点目标等级,原层行判定作废;达成节奏重算归数值批);③requires 前置满足——点亮前置(前置节点全部已点亮)+ **等级阈值前置(requires_level:指定前置节点等级 ≥ n——R1 机制位 2026-10-13 顺带重立,2026-10-12 曾按卡片默认裁撤案执行、用户推翻补机制位;现役 12 行零配置空转,配置归表行随需填)**;④Fork 节点未解锁(一节点双态——解锁后的态切换走零币切换口,不经购买)。执行购买时另查钥石余额:余额不足则购买失败,存档零副作用(等级、余额、激活态均不动)。
写口唯一:营地界面只发购买意图信号(tree_buy_requested),扣币、升级、写盘全部经 MetaProgression.tree_buy;Fork 节点购买走 tree_buy_fork(购即解锁、默认 A 态),态切换走 tree_toggle_fork(零币、只改激活态)。
写口唯一:营地界面只发购买意图信号,扣币、升级、写盘全部经元进度系统的树上购买写口;Fork 节点购买走树上分叉购买写口(购即解锁、默认 A 态),态切换走树上分叉切换写口(零币、只改激活态)。
### Fork 三组:一节点双态与免费切换
@@ -108,11 +108,11 @@ Fork 组是封闭词表三组(2026-10-11 裁:克制专精整组砍除,类
|---|---|---|---|
| fg_posture 开局姿态 | 先手压制 = 每关首轮 AP +1 | 稳健拓荒 = 每关首轮内受击减伤 | 两侧同收**每关首轮窗**(每关第一场战斗的第一轮,第二关自动刷新——时间范围一致性裁甲) |
| fg_sense 危险直觉 | 锋 = 踩中危险格伤害减半·每关限次 | 守 = 危险格标记错误返还行动点·每关限次 | 唯一根植排雷半边的组;玩家文案沿用「危险格 / 安全格」正主词,不出现雷字、不另造别名(2026-10-11 歧义扫描裁甲,废「陷阱/危险标记」自造词)。**前提核实在册**:标记错误机制存在性随实装批 T0 实查,查无则守侧换安全网向等价效果经用户裁 |
| fg_economy 财富之道 | 聚 = 战斗掉落 GP 增益 | 散 = GP 消费折价 | 范围(2026-10-11 歧义扫描裁):聚 = **仅战斗掉落渠道**;散 = **全部 GP 消费面**。**折扣定义边界(2026-10-12 卡片 v1.7 ⑪②裁决写入文档——原「设计避让」句按裁决改写)**:散的折价只覆盖商品购买价(键 `gp_cost_mult`),不含三选一付费刷新与商店刷新(`shop_refresh_cost` 不折)——这是折扣的定义边界本身,非对外两处旋钮的临时避让 |
| fg_economy 财富之道 | 聚 = 战斗掉落 GP 增益 | 散 = GP 消费折价 | 范围(2026-10-11 歧义扫描裁):聚 = **仅战斗掉落渠道**;散 = **全部 GP 消费面**。**折扣定义边界(2026-10-12 卡片 v1.7 ⑪②裁决写入文档——原「设计避让」句按裁决改写)**:散的折价只覆盖商品购买价(消费折价系数),不含三选一付费刷新与商店刷新(`shop_refresh_cost` 不折)——这是折扣的定义边界本身,非对外两处旋钮的临时避让 |
「每关限次」的计数生命周期(2026-10-11 歧义扫描裁甲):限次计数随单次冒险持久、按关重置、新冒险清零、断点续玩保留。
A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 RULE_KEYS 六键——**已随迭代 21 T3 扩签落地**:posture 双键收每关首轮窗、sense 锋/守双键限次按关重置、economy 聚/散双键接线),效果按激活态取值,不接数值线。两侧的设计判据是「不同强、非强弱」:MC 配对锚验收两侧关卡完成率差 <~3pp(**迭代 21 V19 已验:perfect+max 档三组配对差全部 0.0pp 在带**)。
A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表六键——**已随迭代 21 T3 扩签落地**:posture 双键收每关首轮窗、sense 锋/守双键限次按关重置、economy 聚/散双键接线),效果按激活态取值,不接数值线。两侧的设计判据是「不同强、非强弱」:MC 配对锚验收两侧关卡完成率差 <~3pp(**迭代 21 V19 已验:perfect+max 档三组配对差全部 0.0pp 在带**)。
层级纪律(红线):局外树只放全局规则件——effects 禁 archetype 键(schema 加载致命);规则改写型特性(流派件)只走局内随机渠道,永不入树。
@@ -124,40 +124,40 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R
存档 v5 起树域两键(meta 域下):tree_nodes = node_id→已购等级(Fork 节点购买态=1 级解锁标记);fork_sides = fork_group→激活态("a" / "b")。承载与读写归[存档序列化](../表现与支撑/存档序列化.md),本文登记内容语义。
**旧档废弃(2026-10-11 用户裁:开发阶段无迁移)**——Alpha 未出、无存量玩家,结构换代 = 破坏性废弃:加载检测到换代前结构(已删节点 id 残留)即废弃树域重置(或整档废弃),**禁做数据迁移与折算**;三体系迁移函数 `migrate_tree` 与旧域 `forge_levels` 随实装批清退删除。
**旧档废弃(2026-10-11 用户裁:开发阶段无迁移)**——Alpha 未出、无存量玩家,结构换代 = 破坏性废弃:加载检测到换代前结构(已删节点 id 残留)即废弃树域重置(或整档废弃),**禁做数据迁移与折算**;三体系迁移函数与旧域 `forge_levels` 随实装批清退删除。
### 跨局注入:树效果怎么进下一局
树的效果经两条通道随冒险开始参数面下传(通道现行事实源 = [元进度](元进度.md)「跨局注入」节):
① 属性通道——开局属性 = **角色基础值 + 树生命/命中淬炼 flat(Σ 节点 effects × 等级)+ 锻造路线加值(当前武器攻击加值 + 当前甲 AC 加值,只按当前选中生效,正主 = [锻造工坊](锻造工坊.md))+ 局内加算区 flat**(2026-10-11 攻防所有权切分叠加公式换代:树脊柱只持生命/命中,攻/AC 唯一来源=锻造路线——每根数值轴全游戏单一来源;旧「柜台三档 flat」项随柜台退役移除,树淬炼成账号全局基础层、锻造路线成专精层,两层相加)。写入覆盖值通道(atk / max_hp / armor_class),开局显式传入战斗初始化。命中线已购等级已记但未入注入面——消费面(命中折扣档)归数值批(代码批注在册披露)。
① 属性通道——开局属性 = **角色基础值 + 树生命/命中淬炼 flat(Σ 节点 effects × 等级)+ 锻造路线加值(当前武器攻击加值 + 当前甲 AC 加值,只按当前选中生效,正主 = [锻造工坊](锻造工坊.md))+ 局内加算区 flat**(2026-10-11 攻防所有权切分叠加公式换代:树脊柱只持生命/命中,攻/AC 唯一来源=锻造路线——每根数值轴全游戏单一来源;旧「柜台三档 flat」项随柜台退役移除,树淬炼成账号全局基础层、锻造路线成专精层,两层相加)。写入覆盖值通道(攻击 / 生命上限 / 护甲等级),开局显式传入战斗初始化。命中线已购等级已记但未入注入面——消费面(命中折扣档)归数值批(代码批注在册披露)。
② 规则通道——tree_rule_keys = 各 Fork 组激活态的效果键集,经冒险开始参数面 tree_rules 下传;每场战斗 begin_battle 时由效果引擎 rebuild 合成订阅行生效。开新冒险与继续上局两入口同参同值。
② 规则通道——各 Fork 组激活态的效果键集经冒险开始参数面下传;每场战斗开始时由效果引擎重建合成订阅行生效。开新冒险与继续上局两入口同参同值。
### 已建未接面(接线落地态——2026-10-13 迭代 21 反哺写入换代)
三条原「已建表/已可购但消费面未接」的实态,**两条已随迭代 21 接线在役、一条维持纪律**:
- 同调位数量判定源——**已接(迭代 21 T4)**:`Attunement.slots` 判定源已切树点亮数(att_socket1..3),旧条件表(按 Boss 击杀次数自动涨)已随迭代 21 T5 删除(attunement_growth.tres 谓词表退役)。
- 刷新升级节点——**已接(迭代 21 T4)**:树上购买(ks_refresh)同路由 `buy_attune_refresh`,价格/cap 单一事实源 = Y 行表值;双入口并存(树上节点与秘宝匣装配段升级钮写同一笔,Q1 裁)。
- 命中线 flat 不入注入面——**维持(契约 6 纪律)**:override 无 hit 字段,命中未注入;消费面开启归数值批(M4)。
- 同调位数量判定源——**已接(迭代 21 T4)**:同调位判定源已切树点亮数(同调位 1/2/3 三节点),旧条件表(按 Boss 击杀次数自动涨)已随迭代 21 T5 删除(attunement_growth.tres 谓词表退役)。
- 刷新升级节点——**已接(迭代 21 T4)**:树上购买(刷新升级节点)同路由营地刷新升级写口,价格/cap 单一事实源 = Y 行表值;双入口并存(树上节点与秘宝匣装配段升级钮写同一笔,Q1 裁)。
- 命中线 flat 不入注入面——**维持(契约 6 纪律)**:注入参数面无命中字段,命中未注入;消费面开启归数值批(M4)。
## 依赖与输出
| 方向 | 系统 / 文档 | 接口要点 |
|---|---|---|
| 上游 | [元进度](元进度.md) | 钥石——树是永久强化的承载结构与营地本体;消费经钥石余额(写口 SaveManager),注入通道现行事实源在对方;旧柜台退役面在对方登记 |
| 上游 | [元进度](元进度.md) | 钥石——树是永久强化的承载结构与营地本体;消费经钥石余额(写口存档系统),注入通道现行事实源在对方;旧柜台退役面在对方登记 |
| 上游 | [数据配置](../表现与支撑/数据配置.md) | meta_tree.tres 承载与 schema 校验机制——本系统为结构 owner、数值回填归数值批 |
| 上游 | [存档序列化](../表现与支撑/存档序列化.md) | tree_nodes / fork_sides 持久化与旧档废弃承载 |
| 上游 | [秘宝](秘宝.md) | 同调位槽位判定源 = 树点亮数(**已切换在役,迭代 21 T4**;旧条件表已删);秘宝匣工作间内目录与装配现行事实源在彼 |
| 上游 | [锻造工坊](锻造工坊.md) | 武器/护甲路线升级现行事实源在彼——树持钥匙节点(无前置),工作间柜台=钥匙展开面板;攻/AC 数值唯一来源在彼(所有权切分红线) |
| 下游 | [冒险结构](../冒险循环/冒险结构.md) | 冒险开始参数面——树生命/命中淬炼 flat(叠加进覆盖值通道)与 tree_rules 键集的注入通道 |
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 营地四柜台并排呈现——树淬炼柜台=树脊柱界面形态(案甲);信号 = tree_buy_requested / tree_fork_toggled |
| 下游 | 战斗侧效果引擎 | 规则键消费面——begin_battle rebuild 合成订阅(机制硬编码,无表行) |
| 下游 | [冒险结构](../冒险循环/冒险结构.md) | 冒险开始参数面——树生命/命中淬炼 flat(叠加进覆盖值通道)与规则键集的注入通道 |
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 营地四柜台并排呈现——树淬炼柜台=树脊柱界面形态(案甲);信号 = 树上购买意图 / 树上分叉切换意图 |
| 下游 | 战斗侧效果引擎 | 规则键消费面——每场战斗开始时重建合成订阅(机制硬编码,无表行) |
## P0 最小闭环
**P0 语义已随迭代 21 全量落地(2026-10-12 入库;2026-10-13 反哺写入换代——原「33 节点现役态维持」过渡态终结)**:12 节点结构(脊柱 = 生命/命中一线一节点、Fork = 三节点双态)+ 营地统一(树 = 营地本体、生命/命中唯一入口、攻防归锻造工坊、工作间钥匙、四柜台呈现)+ 三组 Fork 双态真效果 + 购买/切换/门槛/彩蛋/注入/旧档废弃全链,机检 = 冒烟 886/0 + run_all 22 套 + probe 全绿 + MC 三组配对差 0.0pp(迭代 21 出口闸读数);残留人闸 = 实机目检营地四柜台(体验验证清单 ⑳)。旧 33 节点结构随 `TREE_STRUCT_VERSION=2` 废弃重置(禁迁移,Q3 裁)。
**P0 语义已随迭代 21 全量落地(2026-10-12 入库;2026-10-13 反哺写入换代——原「33 节点现役态维持」过渡态终结)**:12 节点结构(脊柱 = 生命/命中一线一节点、Fork = 三节点双态)+ 营地统一(树 = 营地本体、生命/命中唯一入口、攻防归锻造工坊、工作间钥匙、四柜台呈现)+ 三组 Fork 双态真效果 + 购买/切换/门槛/彩蛋/注入/旧档废弃全链,逐条判据见「验收标准」节;残留人闸 = 实机目检营地四柜台(体验验证清单 ⑳)。旧 33 节点结构随树结构版本换代废弃重置(禁迁移,Q3 裁)。
## P1 / P2 后置
@@ -174,23 +174,21 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R
| 表 / 常量 | 键结构归属 | 数值行 |
|---|---|---|
| 节点表 `data/tables/meta_tree.tres`(换代后 12 行:价格 / 等级上限 / requires(点亮前置)/ **requires_level(等级阈值前置——R1 机制位,现役零配置)** / effects 取值(Fork 行持 a/b 双效果键)/ 限次值;row 字段随一线一节点退役) | schema MetaTreeTable,本文 owner | 全 provisional,归数值批投值 |
| 脊柱行门槛常量 SPINE_ROW_GATE = 4 | `meta_tree_table.gd` 常量,本文 owner | 常量即规则(节点第 N 级需全树总等级 ≥ 4×(N−1)) |
| Fork 组词表 FORK_GROUPS(三组)/ 规则键词表 RULE_KEYS(换代后六键,扩表随实装批) | `meta_tree_table.gd` 封闭词表 | 词表即规则;规则键效果值硬编码于效果引擎 rebuild |
| 节点表 `data/tables/meta_tree.tres`(换代后 12 行:价格 / 等级上限 / requires(点亮前置)/ **requires_level(等级阈值前置——R1 机制位,现役零配置)** / effects 取值(Fork 行持 a/b 双效果键)/ 限次值;row 字段随一线一节点退役) | schema 声明,本文 owner | 全 provisional,归数值批投值 |
| 脊柱行门槛 = 4 | 节点表结构声明持有,本文 owner | 规则即门槛(节点第 N 级需全树总等级 ≥ 4×(N−1)) |
| Fork 组词表(三组)/ 规则键词表(换代后六键,扩表随实装批) | 三组 Fork 词表与六键规则词表由节点表结构声明持有(封闭词表) | 词表即规则;规则键效果值由效果引擎合成 |
| 钥石产出、刷新升级行族 | 现行事实源 = [元进度](元进度.md)调参表 | `data/tables/meta.tres` |
## 验收标准
> **落地实态(2026-10-13 反哺写入注)**:下列各条已随迭代 21 实装并过出口闸——机检 = 冒烟 886/0(V1-V10/V16 断言顺带)+ run_all 22 套(tree_rules_v11_test 六键闭集)+ probe --click/--full 八探针 + render_gate + MC 三组同种子配对差 0.0pp(V19);残留人闸 = 实机目检营地四柜台(体验验证清单 ⑳)。命中线「暂不注入」括注 = 契约 6 纪律维持(非缺陷——消费面开启归数值批)。
- 结构封闭:meta_tree.tres 通过 schema 校验零违例——12 节点、脉 / 类型 / 币词表出域即拒、Fork 三组每组恰一节点且持 a/b 双效果键、彩蛋 ≤3、effects 键封闭、零 archetype 键。
- 行门槛可判:全树已购总等级 < 4×(N−1) 时脊柱节点第 N 级不可购;总等级达标当刻该级开放购买。
- 前置可判(点亮前置):requires 任一未点亮则节点不可购,逐一点亮后可购;锻造工坊钥匙无前置——初始即可点亮(free 零币)。**等级阈值前置(requires_level):配置了该键的节点,指定前置节点等级 < n 时不可购、达标当刻开放(单测八断言承载——schema 四拒一过+判定三态+投影两态;现役零配置,判定面经测试夹具直构行验证)。**
- 前置可判(点亮前置):requires 任一未点亮则节点不可购,逐一点亮后可购;锻造工坊钥匙无前置——初始即可点亮(free 零币)。**等级阈值前置:配置了该键的节点,指定前置节点等级 < n 时不可购、达标当刻开放(现役零配置,判定面已单独验证)。**
- 工作间可判:钥匙节点(锻造工坊 / 秘宝匣)未点亮时对应柜台锁定态、工作间不可进入;点亮后展开;图鉴无门槛常开。
- 单币可判:任一付费节点购买只扣钥石;余额不足购买失败,且存档零变化(等级、余额、激活态均不动)。
- 双态可判:Fork 节点购买解锁后默认 A 态;切换零币,切换后规则键集=新激活态效果键;同态重复切换零操作;未解锁 Fork 不可切换、不可重复购买。
- 彩蛋可判:生命/命中两节点均买满(4 级拉满)且同调位 1/2/3 全部解锁时彩蛋节点自动点亮且零消费;任一条件不满足则不亮。
- 注入可判:开局覆盖值 = 角色基础值 + 树生命/命中淬炼 flat(命中未注入)+ 锻造路线加值 + 局内加算区(各层语义清晰可加);tree_rules = 全部 Fork 组激活态效果键集;开新冒险与继续上局两入口参数同值;全新存档(树全零)开局与零树状态完全一致。
- 注入可判:开局覆盖值 = 角色基础值 + 树生命/命中淬炼 flat(命中暂不注入——消费面开启归数值批,契约 6 纪律维持、非缺陷)+ 锻造路线加值 + 局内加算区(各层语义清晰可加);规则键集 = 全部 Fork 组激活态效果键集;开新冒险与继续上局两入口参数同值;全新存档(树全零)开局与零树状态完全一致。
- 旧档废弃可判:加载换代前结构存档 → 树域废弃重置 → 后续游玩正常且无残留脏节点(开发阶段纪律,禁迁移);全新存档(树全零)开局与零树状态完全一致。
## 待办
+20 -20
View File
@@ -1,12 +1,12 @@
# 生涯
> **一句话定位**:营地里与三柜台平级的**局外只读界面**——营地管「花」(花钥石买永久强化),生涯管「看」(看自己跨局攒下的一切:累计战绩、历史冒险、图鉴收集)。冒险进行中不给任何数据回看(2026-10-04 用户裁决 P-C7 的延续),生涯是历史数据唯一的家。
> **状态**:期一已实装(2026-10-07 迭代 18 卡④生涯界面实装批·期一——跨局汇总页+主菜单入口+终身计数域存档 v6 落地;期二/期三另行立项,本文不预占排期)。入口拓扑已随迭代 33 卡② 三入口化换代、营地里经笔记本接入归迭代 34 卡④(信息架构节 v1.1,2026-10-22 反哺写入)。现行为 v1.2(2026-10-28 迭代 39 反哺——历史回看节行号指路句改语义指认)。
> **状态**:期一已实装(2026-10-07 迭代 18 卡④生涯界面实装批·期一——跨局汇总页+主菜单入口+终身计数域存档 v6 落地;期二/期三另行立项,本文不预占排期)。入口拓扑已随迭代 33 卡② 三入口化换代、营地里经笔记本接入归迭代 34 卡④(信息架构节 v1.1,2026-10-22 反哺写入)。现行为 v1.3。
> **来源**:[待办清单「『生涯』界面——冒险中数据回看的唯一承接处」条](../../../../待办清单.md)(2026-10-04 用户裁决 P-C7——冒险中不给数据回看,历史战报与结算明细的查看去向 = 新「生涯」界面)。
## 一、信息架构
生涯入口经营地接入(目标结构=营地笔记本,迭代 34 卡④承载;迭代 33 卡② 三入口化后主菜单无生涯钮,现期无玩家可见入口,内部路径直调可达)。现行主菜单按钮组由 `build_start_overlay` 构造(`game/presentation/ui_assembler.gd:97`),只有三个一级钮(迭代 33 卡②,用户裁决 1):「进入存档」「设置」「退出游戏」——「进入存档」为唯一进度入口,点击打开选档界面(3 存档位,迭代 33 卡③)。
生涯入口经营地接入(目标结构=营地笔记本,迭代 34 卡④承载;迭代 33 卡② 三入口化后主菜单无生涯钮,现期无玩家可见入口,内部路径直调可达)。现行主菜单只有三个一级钮(迭代 33 卡②,用户裁决 1):「进入存档」「设置」「退出游戏」——「进入存档」为唯一进度入口,点击打开选档界面(3 存档位,迭代 33 卡③)。
```
主菜单(开始覆盖层,三入口)
@@ -15,8 +15,8 @@
├─ 设置
└─ 退出游戏
调试浮窗(发布态恒隐:`_debug_entry` 写死行已随迭代 38 卡⑦ 删除,开始层与战斗屏/关卡地图屏均零调试入口与角标;
开发期临时启用=本地改 `_debug_entry` 缺省、不提交)
调试浮窗(发布态恒隐:调试入口的写死行已随迭代 38 卡⑦ 删除,开始层与战斗屏/关卡地图屏均零调试入口与角标;
开发期临时启用=本地改调试入口缺省、不提交)
营地(存档内,进档/新档落点)
└─ 生涯(局外只读面;入口=营地笔记本,迭代 34 卡④承载)
@@ -40,24 +40,24 @@
| 内容 | 数据源 | 现状 |
|---|---|---|
| 冒险总局数 / 通关局数 | `adventure_total` / `adventure_cleared` 两终身计数域(save v6——期一已落地) | 现成(2026-10-07 迭代 18 卡④期一实装) |
| 各关完成次数 | `clear_count(level_index)`(`save_manager.gd:238`) | 现成 |
| Boss 首杀与总击杀 | `boss_first_kill_marked`(`save_manager.gd:242`)/ `boss_kill_count`(`:250`) | 现成 |
| 钥石余额 | `keystone_balance`(`save_manager.gd:202`) | 现成。累计收支流水不在存(加钥石与花钥石只动余额,`:206`/`:211`)——如要做属新增流水域,期一实装批另呈报,本文只登记 |
| ~~三柜台解锁完成度概览~~ | (整行删——2026-10-12 用户裁:生涯页不统计天赋树与账号成长态,只统计终身计数;数据源 forge_level/workshop_status/tree_nodes 三投影随天赋树实装批退役,体验验证清单⑲两观测项同步摘除) | — |
| 各关完成次数 | 存档域 `level_clear_counts`(按关卡序号计) | 现成 |
| Boss 首杀与总击杀 | Boss 首杀标记(存档域 `boss_first_kill`)/ Boss 击杀总数(存档域 `boss_kill_count`) | 现成 |
| 钥石余额 | 存档域 `keystone_balance`;累计收支流水不入存档 | 现成。加钥石与花钥石只动余额——如要做属新增流水域,期一实装批另呈报,本文只登记 |
| ~~三柜台解锁完成度概览~~ | (整行删——2026-10-12 用户裁:生涯页不统计天赋树与账号成长态,只统计终身计数;数据源属性锻造等级/工坊状态/树节点三投影随天赋树实装批退役,体验验证清单⑲两观测项同步摘除) | — |
### 历史冒险回看(期二)
数据面现役事实:每局冒险收束时已有逐局摘要自动入档——`recent_summaries`(默认域 `save_manager.gd:116`、值拷贝访问器 `:316`、入账原语 `:325-338` 逐局插最前并按上限截断),上限 `SUMMARY_CAP := 10`(`:40`)。摘要字段在冒险收束时组装(`adventure_runner` 冒险收束受理 `_settle`:结局 result、完成关数 levels_cleared、止步关 stopped_at、钥石合计 keystone_total、本局主种子 master_seed,另附逐杀掉落 keystone_drops 与终局专长持有集 relics(存档键名沿用历史形态)等;钥石与逐杀明细、真视计数、特性持有集取数经主角账两件〔HeroStats/GainJournal〕只读投影——2026-10-27 迭代 39 拆件后形态);摘要的内容口径 owner = [冒险结构](../冒险循环/冒险结构.md),本文不复述其口径。
数据面现役事实:每局冒险收束时已有逐局摘要自动入档——存于存档域 `recent_summaries`:新局插最前、按上限截断,上限 10 条。摘要字段在冒险收束时组装(冒险收束受理:结局 result、完成关数 levels_cleared、止步关 stopped_at、钥石合计 keystone_total、本局主种子 master_seed,另附逐杀掉落 keystone_drops 与终局专长持有集 relics(存档键名沿用历史形态)等;钥石与逐杀明细、真视计数、特性持有集取数经主角账两件〔属性账 / 获得日记〕只读投影——2026-10-27 迭代 39 拆件后形态);摘要的内容口径 owner = [冒险结构](../冒险循环/冒险结构.md),本文不复述其口径。
2026-10-06 用户裁(P2 甲):每局一行摘要,行内给**结局 / 到达关卡 / 钥石收入 / 用时**四件事,上限存最近 N 局。对应三件实绩(本批代码级复核确认的缺口,非新建数据面):
1. **扩容**:cap 10 → N。N 候选 20 / 50 / 维持 10,实装期按存档体积读数三选一;读数方法与列表形态的参数化维度由设计稿给出、实装期执行。
2. **补「用时」埋点**:全仓现在没有任何用时采集点(`game/core/` 内 grep elapsed / ticks / duration / Time 零命中;摘要字段集〔`adventure_runner` 收束受理组装面〕无此项)——属新增埋点,随期二。
2. **补「用时」埋点**:全仓现在没有任何用时采集点(摘要字段集〔冒险收束受理组装面〕无此项)——属新增埋点,随期二。
3. **旧档迁移**:旧档既有 ≤10 条摘要原样保留迁移,向后兼容。
单局结算明细回看:遗留件 `settlement_review_overlay.gd`(`game/presentation/views/`,文件本体在盘、全仓无实例化引用——仅 `ui_kit.gd:356` 样式注记提及)由期二实装批复活承接,改挂生涯(2026-10-06 用户裁 P5 甲)。
单局结算明细回看:遗留的单局结算明细界面本体仍在、未接入任何入口,由期二复活承接,改挂生涯(2026-10-06 用户裁 P5 甲)。
可选增强(不占期、不上列):关键构筑列——摘要已附终局专长持有集 relics(`adventure_runner.gd:825`——存档键名沿用历史形态),实装期评估是否呈现。
可选增强(不占期、不上列):关键构筑列——摘要已附终局专长持有集(存档键 relics,存档键名沿用历史形态),实装期评估是否呈现。
### 图鉴(期三)
@@ -69,11 +69,11 @@
| 界面 | 管什么(现状指证) | 与生涯的边界 |
|---|---|---|
| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树)+ 底栏「最近冒险」条带——`camp_overlay_view.gd:431` `_rebuild_recent()` 逐局拼行(`:442`「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源 = `camp_shop_presenter.gd:85` 投影的同一份 `recent_summaries` | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带的去留是本提纲必答项——见 §五 P6 |
| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树)+ 底栏「最近冒险」条带——营地底栏把摘要逐局拼行(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」)、无截断上限,数据源与生涯历史回看同属一份最近摘要 | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带的去留是本提纲必答项——见 §五 P6 |
| 状态面板(迭代 17 卡③,并行批开发中) | **局内**只读查看这一局进行中的钥石与本局冒险进度 | 状态面板看当局当前值,生涯看跨局累计与历史;局内无生涯入口(P4 甲) |
| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——`main.gd:408` 的 `_on_adventure_finished`(实体在 `main_adventure_flow.gd:84`)收束时呈现摘要与钥石明细(复用终局面账行,`battle_end_overlay.gd:118-129`) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册,`ui_assembler.gd:99` 注记) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 |
| 图例面板(legend_overlay) | 局内符号/徽标即时解码 | 不合并——图例是局内解码面,图鉴是跨局收集面 |
| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 |
| 图例面板 | 局内符号/徽标即时解码 | 不合并——图例是局内解码面,图鉴是跨局收集面 |
## 四、分期实装建议
@@ -81,11 +81,11 @@
| 期 | 内容 | 前置 | 建议窗口(候选) |
|---|---|---|---|
| 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 schema 迁移——**✅ 已实装(2026-10-07 迭代 18 卡④)**:汇总段六类九项+营地与系统组生涯钮+存档 v6 两终身计数域〔v5 升 v6 幂等迁移〕;闸读数与代拍板追认单原记于该批出口报告,该件已按「吸收即死」在提交 8e02dcf0 删除,读数以本行与该批提交信息为准 | 提纲评审通过。实装批前置义务 = 对齐迭代 16 UI 批后的现行基线:`FS_*` 六档字号 / `TG_*` 语义色 / `BUTTON_MIN_H=40`(`ui_kit.gd:437`)/ 色板单源 / `page_shell` 模态栈纪律(`ui_kit.gd:394`)——与卡③需求卡片同款红线,本文只登记、不展开 | Alpha 后最近一个 UI 窗口 |
| 期二 | 历史冒险回看整段:N 扩容 + 用时埋点 + 旧档摘要迁移 + 逐局列表 + 单局明细(`settlement_review_overlay.gd` 复活承接)+ 营地条带处置(P6 裁案落地) | 期一。实装批开工前须向用户呈报两件:N 终值读数(20 / 50 / 维持 10 三选一)与「用时」埋点口径(计时起点、暂停是否计入——字段级细节本文不做) | 期一落地后独立批 |
| 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 schema 迁移——**✅ 已实装(2026-10-07 迭代 18 卡④)**:汇总段六类九项+营地与系统组生涯钮+存档 v6 两终身计数域〔v5 升 v6 幂等迁移〕;闸读数与代拍板追认单原记于该批出口报告,该件已按「吸收即死」删除,读数以本行与该批提交信息为准 | 提纲评审通过。实装批前置义务 = 对齐迭代 16 UI 批后的现行基线:字号六档 / 语义色 / 按钮最小高 40 点等现行界面基线 / 色板单源 / 页面壳的模态栈纪律——与卡③需求卡片同款红线,本文只登记、不展开 | Alpha 后最近一个 UI 窗口 |
| 期二 | 历史冒险回看整段:N 扩容 + 用时埋点 + 旧档摘要迁移 + 逐局列表 + 单局明细(遗留明细界面复活承接)+ 营地条带处置(P6 裁案落地) | 期一。实装批开工前须向用户呈报两件:N 终值读数(20 / 50 / 维持 10 三选一)与「用时」埋点口径(计时起点、暂停是否计入——字段级细节本文不做) | 期一落地后独立批 |
| 期三 | 图鉴承接(敌人优先,范围与顺序随 P3 甲);「见过」记账面 schema 义务随该批执行 | 期二或独立评估 | Beta 窗口候选(与教学引导同段评估——图鉴的「认得」轴与教学相关;待办清单教学引导节各条 2026-10-04 起已整体标 Beta 阶段) |
本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装——`game/` 内 grep TranslationServer 零命中;文案权先例 = 教学文案定稿权登记于待办清单),实装批承接。
本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装;文案权先例 = 教学文案定稿权登记于待办清单),实装批承接。
## 五、待裁点
@@ -93,7 +93,7 @@ P0-P5 六点已于 2026-10-06 用户全裁(案甲 × 6,备选案与淘汰依
### P6 · 营地底栏「最近冒险」条带去留(期二必答项)
**现状与问题**:营地底栏「最近冒险」条带把传入的摘要逐行全部拼进一个 Label(`camp_overlay_view.gd:431-443`,`:442` 行模板,无截断上限),数据源与生涯历史回看是同一份 `recent_summaries`(`camp_shop_presenter.gd:85`)。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。
**现状与问题**:营地底栏「最近冒险」条带把摘要逐行全部拼进同一个标签(行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源与生涯历史回看同属一份最近摘要。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。
| 案 | 内容 | 淘汰/保留依据 |
|---|---|---|
+4 -4
View File
@@ -1,13 +1,13 @@
# 秘宝
> 一句话:营地装配、整局冒险生效的角色机能件——操作包络加强(视野/行动点/刷新/挖掘距离等),不出角色战斗属性值、不改战斗结算机制(经济与操作参数——视野/距离/行动点/刷新/转存率——属操作包络;分界规则归[核心循环](../../01-核心循环.md)「道具三层职责与分界」节)
> 状态:已定稿(现行形态=局外同调件——2026-10-03 迭代 11 第①批同调实施批已入库)
> 状态:已定稿(现行形态=局外同调件——2026-10-03 迭代 11 第①批同调实施批已入库);现行为 v1.1
> 定位与形态:见下节
> 术语:本文件的项目术语(同调位/RAP/GP/真视/危险格/整图等)定义见[术语表](../../术语表.md)
## 定位与形态
**现行形态 = 局外同调件**:开局前在营地装配(营地·秘宝匣柜台「同调装配」段)、占同调位(0 起步、固定 3 格,每件固定占 1 格)、被动件整局生效/主动件每冒险充能(冒险开始重置)。实现面 = `treasure_items.tres` + `game/core/attunement.gd`(同调位数量判定源=天赋树同调位节点点亮数——2026-10-11 Q2 裁甲、迭代 21 T4 槽位收编落地;原 `attunement_growth.tres` 谓词表已顺带退役;解锁条件=数据行内 `unlock_requires` 字段承载——迭代 22 实装落地,2026-10-12)。**新目录 12 件已随迭代 22 实装批全量落地(2026-10-12,T1-T7)**,逐件实录见「目录(重设计 v1)」各表实态列。
**现行形态 = 局外同调件**:开局前在营地装配(营地·秘宝匣柜台「同调装配」段)、占同调位(0 起步、固定 3 格,每件固定占 1 格)、被动件整局生效/主动件每冒险充能(冒险开始重置)。实现面 = `treasure_items.tres` + 同调系统件(同调位数量判定源=天赋树同调位节点点亮数——2026-10-11 Q2 裁甲、迭代 21 T4 槽位收编落地;原 `attunement_growth.tres` 谓词表已顺带退役;解锁条件=数据行内解锁条件字段承载——迭代 22 实装落地,2026-10-12)。**新目录 12 件已随迭代 22 实装批全量落地(2026-10-12,T1-T7)**,逐件实录见「目录(重设计 v1)」各表实态列。
**沿革一句话**:本系统前身为切片期掉落消耗品形态(2026-09-29 迭代 7),2026-10-03 随同调实施批整体替换为现行形态;旧目录九件已全数退役入素材库(2026-10-08/10 用户裁)。历史细节见 git 提交史。
@@ -15,7 +15,7 @@
- **同调位分档**(2026-10-07 用户裁):同调位分三档=普通格/优秀格/稀有格;装配规则=**槽位可装 ≤ 槽位档位的件**(向下兼容,不惩罚早解锁);格品质随天赋树同调位节点递增(1=普通格/2=优秀格/3=稀有格,2026-10-10 用户裁;树侧接线=迭代 21 T4 槽位收编落地)。装配面呈现槽位档位(迭代 22 秘宝匣柜台落地)。
- **旧目录退役指针**:旧目录九件、魔法提灯、机制向三件处置已全数退役入素材库(表一 14 件)——现行目录见下方「目录(重设计 v1)」节;代码旧行已全数删除(迭代 20 先行删 2 行+迭代 22 T1 删 12 行,2026-10-12)。
- **三选一刷新双源**:刷新能力=**存量+临时**两笔系统资源——存量=持久(开局注入基值 0;来源=秘宝匣刷新升级行购买〔meta.tres attune_refresh_upgrade 行族〕+刷新练习件加仓;跨呈现持续、用完即无)、临时量=每次三选一呈现重置为基线 0(来源=效果件的临时注入,未用不结转);**消耗顺序=先扣临时、不足再扣存量**(2026-10-10 用户复述定案;代码字段名 X/Y 与两笔的字母对表归实装批 T0,正文不依赖字母)。重抽复用现有洗牌逻辑(reward_pick),池规则全保留;自动游玩模式默认不刷。**GP 付费档(2026-10-06 用户令)**:免费双源用尽后可 GP 付费刷新,价=ceil(20×1.8^本次停点已付费刷) provisional(与商店货架刷新读同两个调参参数 shop_refresh_*,单点调参);付费刷计数每停点清零;钱包=冒险 GP 同一笔系统资源(唯一入口 LevelRunner.spend_gp);余额不足=钮禁用(不隐藏)。店内专长凭证三选一暂不挂刷新面(扩面须先裁凭证保底计数交互)。**两笔语义**两笔语义的框架定义见[核心循环](../../01-核心循环.md)「生命周期轴」节。「三选一呈现」是比节点更细的事件——奖励节点、Boss 通关奖励、商店(凭证)三处都触发。奇物「刷新次数+1」类资源件的语义=存量一次性加仓。
- **三选一刷新双源**:刷新能力=**存量+临时**两笔系统资源——存量=持久(开局注入基值 0;来源=秘宝匣刷新升级行购买〔meta.tres attune_refresh_upgrade 行族〕+刷新练习件加仓;跨呈现持续、用完即无)、临时量=每次三选一呈现重置为基线 0(来源=效果件的临时注入,未用不结转);**消耗顺序=先扣临时、不足再扣存量**(2026-10-10 用户复述定案;代码字段名 X/Y 与两笔的字母对表归实装批 T0,正文不依赖字母)。重抽复用现有洗牌逻辑,池规则全保留;自动游玩模式默认不刷。**GP 付费档(2026-10-06 用户令)**:免费双源用尽后可 GP 付费刷新,价=ceil(20×1.8^本次停点已付费刷) provisional(与商店货架刷新读同两个调参参数 shop_refresh_*,单点调参);付费刷计数每停点清零;钱包=冒险 GP 同一笔系统资源(唯一入口=冒险 GP 扣账口);余额不足=钮禁用(不隐藏)。店内专长凭证三选一暂不挂刷新面(扩面须先裁凭证保底计数交互)。**两笔语义**两笔语义的框架定义见[核心循环](../../01-核心循环.md)「生命周期轴」节。「三选一呈现」是比节点更细的事件——奖励节点、Boss 通关奖励、商店(凭证)三处都触发。奇物「刷新次数+1」类资源件的语义=存量一次性加仓。
- **🔴 承重契约**(红线;其中真视范围等由数据加载校验强制):效果不允许写成直接加减属性的裸数值——效果类型只能取封闭白名单;真视(直接标出危险格的效果,定义见「核心规则」)单次范围 ≤3×3;核心逻辑不做异步等待;零随机;排雷玩法零特判;危险格与安全格的分布同池冻结——资源类件只能调整权重投影、不能改池结构;**棋盘事实不可变**(秘宝效果不移除、不增改危险格位置与邻接数字——效果形态只允许信息呈现与开格动作;2026-10-10 用户裁,原爆破工兵包「中立移除」即违规形态、随件退役废止)。
- **验收分层**:难度验收分两层——同调系统关闭时沿用现行关卡完成率验收带;开启时验收带按投值平移(归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)投值;商店开关分层同法)。
@@ -228,7 +228,7 @@
## 调参表
道具定义表的全部数值均为 provisional,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)调参;registry `aux_items` 条目已随迭代 22 换代为新目录 12 件登记(2026-10-12);表 schema 归[数据配置](../表现与支撑/数据配置.md)承载、本系统回填。
道具定义表的全部数值均为 provisional,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)调参;`aux_items` 条目已随迭代 22 换代为新目录 12 件登记(2026-10-12);表 schema 归[数据配置](../表现与支撑/数据配置.md)承载、本系统回填。
结构冻结(非调参项,改动走本文修订流程):真视窄域护栏、零信息目标不消耗、边缘窗口照常受理、使用零行动点成本、同调位装配与充能每冒险重置、用后不可逆、零随机。
+17 -44
View File
@@ -1,9 +1,7 @@
# 锻造工坊
> 一句话:营地的武器与护甲成长——武器锻造区三条路线、护甲锻造区三条路线各练各的,钥石消费,一次冒险各选一件,满级特效留位本期不做
> 状态:**v0.9(2026-10-26 迭代 37 结项:实现载体行号引用改指现址,规则与数值零变化);承 v0.8 迭代 21 反哺④残余写入文档(2026-10-13)**——「树侧钥匙」句换代:钥匙节点 forge_workshop 已随迭代 21 实装在役(free 无前置、点亮展开本柜台、锁定态零购买路径),原「实装归待办清单」悬置指针终结。承 v0.7 迭代 23 反哺写入(2026-10-13)——正文补 Q1 等级语义衔接(0 = 未练合法态)、效果类型闭集 24→31 订正、实装边界 1 回填实装实态(已随迭代 23 入库)。承 v0.6 完备性检查批(承 v0.5 B 组定案——**17 裁点全数清零**:1 丙 / 1a 一次冒险一件武器+一件甲 / 1b 营地可换·战斗禁换 / 1c 免费 / 2a 伪裁点消解 / 2b 随 3a 消解 / 3a 魔法护甲统一命名 / 3b 敌方攻击缺省普通·克制反向当下生效 / 3c 每型独立路线 / 4a 前缓后陡 / 4b 锻造内自闭环 / 4b-1 并联双通道 / 5b 纯钥石 / 5c 零材料 / 6 满级特效本期不做留扩展位 / 8a 工作间两区定名;2c、5a 已于 v0.4 结案)。**v0.6 变更(2026-10-12 完备性检查批·grilling 用户逐条裁)**:现役事实基线换代(forge_products 行族已随迭代 21 T5 退役、定价参数改历史考古口径);**两根类型轴所有权移交**(默认值归锻造工坊配置常驻持有、hero.tres 静态表两轴删除、选中态写存档+参数面注入);「与秘宝混合制同构」与「激活穿刺系专长」两处无依据表述订正删除。**满级特效本期不做**(只留扩展位);全部数值 provisional,终值归 Alpha 收官 M4 数值批〔未排期〕;实装已随迭代 23 入库(2026-10-12)
> 成文底稿:提案过程稿《提案-武器锻造系统-2026-10-07》(**已退役**——中间产物吸收即死,2026-10-22 删除;全部备选案与淘汰依据、用户原案逐条对照已逐条落进本文,结论不依赖过程稿;**满级特效候选池已于 2026-10-21 迁入本文「满级特效扩展位」节**;考古=git 历史——删除批 603eba31,删除前全稿见历史版本 f958a03f)
> 状态:**v0.10(2026-10-29 实现内容清理:删「现役代码事实」「实装边界」两节与正文代码指证,规则与数值零变化)**——全部数值 provisional,终值归 Alpha 收官 M4 数值批〔未排期〕
## 设计目的
用户原案(playnote 2026-10-06):把现役"营地花钱买攻击 +1"改造成有等级、有成长、有满级特效的**武器升级路线**;新增长枪(穿刺)与法杖(魔法)实现武器间差异化;护甲同理——初始只有轻甲(升级只提 AC),最高等级后解锁重甲/魔法护甲,基础 AC 各异、各带满级特效。
@@ -29,7 +27,7 @@
|---|---|
| 钥石与营地柜台结构 | [元进度](元进度.md)——本系统即属性锻造柜台攻击/护甲两档的改造,消费货币与柜台框架归彼 |
| 数值终值(每级增量/等级上限/价格/基础 AC/特效值) | Alpha 收官 M4 数值批——本文全部数值只有量纲与锚点,标 provisional |
| 代码与数据表落地 | 实装批(待办在册待办清单)——迁移面见下文,含数值验证义务 |
| 代码与数据表落地 | 实装批(已入库) |
| 克制矩阵与伤害结算 | [战斗结算](../战斗循环/战斗结算.md)——矩阵九格结构冻结是既有条款,本系统零改动 |
| 局内属性奖励件 | [奇物](../冒险循环/奇物.md)——账号层永久强化与单冒险加算区分层并存,互不侵入 |
| 存档读写 | [存档序列化](../表现与支撑/存档序列化.md)——实装后新增等级与当前选中域,旧档废弃口径随实装批(开发阶段无迁移) |
@@ -51,11 +49,11 @@
**工作间内两区并列**(A③ 裁):武器区/护甲区并列呈现,机制层面零顺序门槛;同屏分栏或页签切换=呈现细节归 HUD 批。
**树侧钥匙(2026-10-11 裁:无前置)**:本柜台由天赋树「锻造工坊」钥匙节点展开——free 节点、**无前置条件、直接可点亮**(原「攻击淬炼累计 ≥2 级 且 护甲淬炼累计 ≥2 级」等级阈值前置,随树脊柱砍攻/护两线作废);未点亮时本柜台锁定态,点亮后开放。树侧结构换代(脊柱砍攻/护两线 + 一线一节点 + Fork 一节点双态,三轮换代 **29→21→15→12**,地脉共鸣条件效果随之换代——2026-10-12 订正:原记「29→21」系 v0.6 时点旧数,树侧现行终态 12 节点)设计权威文档 = [天赋树](天赋树.md);**钥匙节点 forge_workshop 已随迭代 21 实装在役(2026-10-12 入库——free 无前置、点亮展开本柜台、锁定态零购买路径),2026-10-13 反哺写入换代原「实装归待办清单」悬置指针**。
**树侧钥匙(2026-10-11 裁:无前置)**:本柜台由天赋树「锻造工坊」钥匙节点展开——free 节点、**无前置条件、直接可点亮**(原「攻击淬炼累计 ≥2 级 且 护甲淬炼累计 ≥2 级」等级阈值前置,随树脊柱砍攻/护两线作废);未点亮时本柜台锁定态,点亮后开放。树侧结构换代(脊柱砍攻/护两线 + 一线一节点 + Fork 一节点双态,三轮换代 **29→21→15→12**,地脉共鸣条件效果随之换代——2026-10-12 订正:原记「29→21」系 v0.6 时点旧数,树侧现行终态 12 节点)设计权威文档 = [天赋树](天赋树.md);**钥匙节点已随迭代 21 实装在役(2026-10-12 入库——free 无前置、点亮展开本柜台、锁定态零购买路径),2026-10-13 反哺写入换代原「实装归待办清单」悬置指针**。
**注入衔接(所有权切分,2026-10-11 裁)**:每根数值轴全游戏单一来源——生命/命中唯一入口=树脊柱;**攻击唯一入口=武器路线、AC 唯一入口=甲路线(本系统)**,树零攻防来源(红线)。锻造路线加值 = 当前武器攻击加值 + 当前甲路线 AC 加值,与树淬炼 flat、局内加算区在开局属性注入面分层相加(通道现行事实源 = [元进度](元进度.md)「跨局注入」节);锻造加值只按**当前选中**的武器/甲生效(长剑的练度在换带长枪后回家待命,不丢级、不生效)。
**两根类型轴的所有权(2026-10-12 v0.6 订正,用户裁)**:`attack_type` / `armor_type` 的默认值由锻造工坊配置**常驻持有**(默认值 = 现役静态表现役值 normal / light),hero.tres 静态表两轴随实装删除——静态表不再存放这两项。冒险开始时参数面读「锻造工坊配置默认值 + 存档当前选中覆盖」注入。**默认值与柜台解锁状态解耦**:树钥匙节点未点亮时柜台为锁定态(玩家无交互入口),但配置表常驻可读、默认值照常注入——不存在「锻造工坊解锁前读不到默认值」的窗口。选中态写存档(与等级域同批新增),与现役「表运行期只读、永久强化写存档」纪律一致(`meta_progression.gd:12-13/119`)。
**两根类型轴的所有权(2026-10-12 v0.6 订正,用户裁)**:`attack_type` / `armor_type` 的默认值由锻造工坊配置**常驻持有**(默认值 = 现役静态表现役值 normal / light),hero.tres 静态表两轴随实装删除——静态表不再存放这两项。冒险开始时参数面读「锻造工坊配置默认值 + 存档当前选中覆盖」注入。**默认值与柜台解锁状态解耦**:树钥匙节点未点亮时柜台为锁定态(玩家无交互入口),但配置表常驻可读、默认值照常注入——不存在「锻造工坊解锁前读不到默认值」的窗口。选中态写存档(与等级域同批新增),与现役「表运行期只读、永久强化写存档」纪律一致。
### 三件武器
@@ -65,10 +63,10 @@
| 长枪 | **长枪锻造** | 穿刺 | 解锁后从 1 级练起 |
| 法杖 | **法杖锻造** | 魔法 | 解锁后从 1 级练起 |
> **改名注(2026-10-12,8a 裁·Y 层)**:六条路线的行名统一为「件名 + 锻造」——长剑锻造 / 长枪锻造 / 法杖锻造、轻甲锻造 / 重甲锻造 / 魔法护甲锻造。其中第一件武器的正式显示名由「剑」改「长剑」(用户原话口径)。`attack_type` 字段值、矩阵语义、registry 攻击类型词条均不受此改名影响——改的只是显示名。
> **改名注(2026-10-12,8a 裁·Y 层)**:六条路线的行名统一为「件名 + 锻造」——长剑锻造 / 长枪锻造 / 法杖锻造、轻甲锻造 / 重甲锻造 / 魔法护甲锻造。其中第一件武器的正式显示名由「剑」改「长剑」(用户原话口径)。`attack_type` 字段值、矩阵语义、攻击类型词条登记均不受此改名影响——改的只是显示名。
- 每件武器有**独立等级**(1 到上限,锚点 5 级量级,provisional;**0 级 = 未练合法态**——未解锁或已解锁未锻造均为 0 加值裸体态,锻出第 1 级 = 首个 +1,Q1 裁 2026-10-12)、独立成长曲线、独立的满级特效挂位(**本期为空位**——6 裁,见「满级特效扩展位」节)——三件武器的等级各自保留,换当前武器不丢任何进度。
- **当前武器单选出场**:类型由武器决定(选长枪=穿刺)。选中态写**存档域**(与等级域同批新增),冒险开始经参数面注入;`attack_type` 的默认值归锻造工坊配置常驻持有、hero.tres 静态表该轴随实装删除(2026-10-12 v0.6 订正:原「写入 hero.tres 休眠轴」口径与现役「表运行期只读、永久强化写存档」纪律冲突——`meta_progression.gd:12-13/119`,且与本文存档域条目自相矛盾;现役静态值 normal 由锻造工坊配置默认值接管)。
- **当前武器单选出场**:类型由武器决定(选长枪=穿刺)。选中态写**存档域**(与等级域同批新增),冒险开始经参数面注入;`attack_type` 的默认值归锻造工坊配置常驻持有、hero.tres 静态表该轴随实装删除(原「写入 hero.tres 休眠轴」口径与现役「表运行期只读、永久强化写存档」纪律冲突,且与本文存档域条目自相矛盾;现役静态值 normal 由锻造工坊配置默认值接管)。
- **一次冒险 = 一个武器类型 + 一个甲型(1a 裁)**:同一时刻只有一件武器、一件甲生效(出场行显示"当前:长剑锻造 + 轻甲锻造")。多持轮换淘汰——棋盘穿刺/魔法直伤件已承载"对不同甲型换着打"的战术价值,战斗内换械要给结算管线新开"类型可变"维度,收益不成比例。(原裁点只问武器携带数,本裁把它扩到"一次冒险"粒度并同时覆盖甲侧。)
- **营地内随时切换、免费(1b / 1c 裁)**:等级都是玩家花资源练出来的,切换只是决定"今天带谁出门";二次收费会惩罚尝试性构筑,与成长曲线诉求(低成本试错找匹配武器)相逆。**冒险开始后锁死**:伤害单据的类型在开单时声明、管线内不变。
@@ -83,7 +81,7 @@
- **命名统一(3a / 2b 裁)**:**只有「魔法护甲」这一个名字**(= 现役甲域 ward)。用户原案里的「魔法甲」是**笔误**,不是别名、不做简称退役处理——正文与界面文案一律写「魔法护甲」([术语表](../../术语表.md) 已登记);仅历史版本行与过程稿引用原文时按记载保留。"第四甲型"不存在也不需要讨论(2b 随之自动消解)。
- **每型独立等级路线(3c 裁)**:与武器路线同构;"当前甲"单选与"当前武器"单选同构,营地内免费切换。
- **甲型差异当下双轴可感(3b 裁——推翻原「显式边界·诚实收窄」条款)**:**敌方攻击是有类型的**;未显式设置的敌方攻击一律按**默认普通(normal)**参与结算(用户裁)。矩阵 normal 行取值 = 轻甲 0 / 重甲 −1 / 魔法护甲 +1(整数点数:+1 被克 / 0 中性 / −1 挡住)。所以甲型差异不是"只有基础 AC 可感",而是 **基础 AC + 克制修正两轴当下同时生效**——不再有"克制反向要等敌方攻击类型批"的推迟。
- **现役实态与本裁定的落差(跨系统汇合点,本系统只登记需求)**:现役敌方攻击单据的 `attack_type` 栏为 `""`(空串恒不查克制矩阵,`damage_controller.gd:19/:550-552`;该栏注释把"敌方技"列入无类型族),`enemy.tres:527` 登记「敌方技能攻击类型轴仍未开(全部 normal 档)」——轴未开、开了也全 normal。**按 3b 裁执行 = 把该轴按「缺省填 normal」启用**;填值动作面落在敌人系统 / 战斗结算侧,不在本工作间内。锻造工坊实装批只登记该需求与它的数值影响(见「实装边界」第 6 条)。
- **现役实态与本裁定的落差(跨系统汇合点,本系统只登记需求)**:现役敌方攻击单据的攻击类型栏为空(空串恒不查克制矩阵,与"群伤/反噬/地形/敌方技"同列无类型族),敌人表登记「敌方技能攻击类型轴仍未开(全部 normal 档)」——轴未开、开了也全 normal。**按 3b 裁执行 = 把该轴按「缺省填 normal」启用**;填值动作面落在敌人系统 / 战斗结算侧,不在本工作间内。本系统只登记该需求与它的数值影响。
- **连带后果(3b 裁的前置信息,用户裁定:不在本批考虑,留作后续开发改造的输入)**:按"敌方攻击缺省普通"落定后,现行数据下三型会成**纯序**——重甲(AC +2 且挡普通 −1)= 双正、轻甲 = 中性、魔法护甲(AC −1 且被普通克 +1)= 负向,且因现行敌方无魔法攻击而暂无优势窗口。这是**后续开发改造的前置事实**不是缺陷裁定:敌方攻击类型的差异化、魔法护甲的正当性由后续批次负责(含 M4 在基础 AC / 解锁价上的取值);本批只需按统一伤害轴把结构与字段落对即可。
- 每 1 点 AC 的期望减伤约 3.75% 量级(现役 EV 复算锚:AC10 期望减免 21.25%、AC11 25%),终值归 M4。
@@ -97,7 +95,7 @@
### 经济
- **纯钥石(5b 裁)**:与现役属性锻造、天赋树(树全钥石单币)、同调刷新升级同币同柜。原案语义是"攻击力提升改造"——同一笔钱换个花法。GP = 局内一次性货币、局内清零不入存档、与钥石永不互兑,两币分工是拍板条款,不引入局外 GP 余额域或第三货币。(用户裁定注:当前没有其他材料,钥石就是统一货币;**后续若要分出专用币或材料,届时再重构设计,不预先建面**。)
- **几何递增定价**:每级价格 = 基础价 × 150%^(已购级数)(历史考古参数:单次 20 钥石、公比 150%——`meta.tres:82-84` 退役批注;forge_products 行族已随迭代 21 T5 退役、无现役计价字段可沿,实装投值归 M4 从零标定);路线解锁 = 一次性解锁价(量纲 ≈ 中段几级锻造花费)。全部终值归 M4。
- **几何递增定价**:每级价格 = 基础价 × 150%^(已购级数)(历史考古参数:单次 20 钥石、公比 150%——退役行批注;forge_products 行族已随迭代 21 T5 退役、无现役计价字段可沿,实装投值归 M4 从零标定);路线解锁 = 一次性解锁价(量纲 ≈ 中段几级锻造花费)。全部终值归 M4。
- **零材料经济(5c 裁)**:不引入账号层锻造材料。现役效果引擎的 grant_material 是**局内**材料计数器(如雷区图谱),不是账号层库存;账号层材料=全新经济面(存档域/获取渠道/囤积规则全套新建),原案全文只字未提材料,武器差异化的价值轴是类型(与预留的特效位)不是采集刷料。
### 满级特效扩展位(本期不启用——6 裁)
@@ -112,55 +110,30 @@
| 路线 | 候选特效(量纲方向) | 实现代价分档(2026-10-21 按现役代码复核) |
|---|---|---|
| 长剑 | 破甲:命中重甲时克制修正额外 +1 点量级 | 零新代码——`modify_ticket` 效果类型与 matchup 条件算子均在现役闭集 |
| 长剑(第二条) | 暴击面扩大:暴击骰面 −1 量级 | **需新参数位**——暴击面现役是表上恒定值(`settlement.tres:52 crit_roll=20`),改成武器级可变=实装批改读表面 |
| 长剑(第二条) | 暴击面扩大:暴击骰面 −1 量级 | **需新参数位**——暴击面现役是表上恒定值(`settlement.tres` 的 `crit_roll=20`),改成武器级可变=实装批改读表面 |
| 长枪 | 先手连击:战斗开场追加一次普攻量级的攻击 | 零新代码——`attack_segments` 在册 |
| 法杖 | 开局魔法矢:每场战斗开场自动对最前敌人放一次魔法直伤(必中全额量级) | 零新代码——`spawn_spell` 在册(次数/回合数 provisional) |
| 轻甲 | 灵巧:掷点折扣临界档再缓一档量级 | **需新参数位**——折扣档现役读表恒定(`settlement.tres:51 roll_tier_discounts=[75,50,25]`) |
| 轻甲 | 灵巧:掷点折扣临界档再缓一档量级 | **需新参数位**——折扣档现役读表恒定(`settlement.tres` 的 `roll_tier_discounts=[75,50,25]`) |
| 重甲 | 反震:被命中时对攻击者反贴 1 点量级伤害 | 零新代码——`spike` 在册 |
| 魔法护甲 | 护盾:每场战斗开场自带护盾(层数 X 量级,终值归 M4) | 零新代码+参数面桥接——`shield` 在册,护盾层数进战斗开场参数需接线 |
- 候选池的实现深度前提(沿用过程稿结论并经现役复核):七条全部可用现役 `EFFECT_KINDS` 闭集词或参数位表达,**无一条需要往闭集加新效果类型词**——加词是高代价变更面(加载校验/文档词表/回归测试三处连带),启用时禁走此路。
- 候选池的实现深度前提(沿用过程稿结论并经现役复核):七条全部可用现役效果类型闭集词或参数位表达,**无一条需要往闭集加新效果类型词**——加词是高代价变更面(加载校验/文档词表/回归测试三处连带),启用时禁走此路。
**对相邻口径的三处连带**:① 设计目的节"能力解锁的节奏"台阶收缩为新武器 / 新甲型解锁(不再含特效);② 实装边界不再有"特效三条新参数位"条目;③ 存档域不设特效域。
**对相邻口径的两处连带**:① 设计目的节"能力解锁的节奏"台阶收缩为新武器 / 新甲型解锁(不再含特效);② 存档域不设特效域。
## 与相邻系统的边界
- **奇物属性件**:分层并存,互不侵入。武器/护甲锻造动的是**账号层基础值**(营地永久强化,冒险开始时经参数面传入);奇物属性件动的是**单次冒险加算区**(关间不清零、冒险结束清零)。本设计不扩奇物"生命/AC/攻击"三轴封闭分类、不动渠道开关表、不往局内池加任何东西。入战 AC 的三层语义清晰可加:甲型基础 AC + 营地甲路线等级加值 + 局内加算区累加。
- **克制矩阵(2a 裁:伪裁点消解)**:**伤害结算只有一套,类型不是待仲裁的槽位**。每份伤害单据自带 `attack_type` 栏(`damage_controller.gd:19` 单据九栏之一)——主角基础攻击单据取 `hero.tres` 的 attack_type(由当前武器决定),棋盘格面直伤件(穿刺弹 / 魔法矢)取 reward 表自身的类型(预期 `reward.tres:131/142`)——各自按**同一张 3×3 矩阵** `armor_effectiveness` 取克制修正(`_counter_delta`)。因此"叠加 / 替代 / 互斥"三分法不成立:两根参数之间无冲突可裁,也不需要任何共存规则。二者的真实差异只在**单据形态与生命周期**(主角基础攻击走掷点折扣 + 暴击;棋盘克制直伤走必中全额不掷点 `_resolve_board_typed`;且前者 = 账号层永久、后者 = 局内一次性)——那是两条不同的伤害来源,不是两种类型语义。矩阵九格满格结构冻结(2b 随 3a 自动消解:魔法护甲 = 现役 ward,不存在第四甲型)。
- **克制矩阵(2a 裁:伪裁点消解)**:**伤害结算只有一套,类型不是待仲裁的槽位**。每份伤害单据自带攻击类型栏——主角基础攻击单据取当前武器的攻击类型,棋盘格面直伤件(穿刺弹 / 魔法矢)取奖励表自身的类型——各自按**同一张 3×3 克制矩阵**取克制修正。因此"叠加 / 替代 / 互斥"三分法不成立:两根参数之间无冲突可裁,也不需要任何共存规则。二者的真实差异只在**单据形态与生命周期**(主角基础攻击走掷点折扣 + 暴击;棋盘克制直伤走必中全额不掷点;且前者 = 账号层永久、后者 = 局内一次性)——那是两条不同的伤害来源,不是两种类型语义。矩阵九格满格结构冻结(2b 随 3a 自动消解:魔法护甲 = 现役 ward,不存在第四甲型)。
- **工作间两区定名 + 路线行名(8a 裁)**:锻造工坊工作间内先立两个分区,各装三条锻造路线(路线行名统一为"件名 + 锻造")——①**武器锻造区**:长剑锻造 / 长枪锻造 / 法杖锻造;②**护甲锻造区**:轻甲锻造 / 重甲锻造 / 魔法护甲锻造。两区并列、机制层面零顺序门槛(A③ 裁),区内路线各自独立升级。
- **局内不存在武器/护甲的提升(8a 裁连带核结)**:用户指出"局内没有可以提升武器的地方"——**属实**。查实:局内商店货架的三行(现役旧名"攻击锻升 / 生命锻升 / 护甲锻升"——**2026-10-12 已改名,见下**),**正是奇物「属性值提升」三轴(攻击 / 生命上限 / AC)的货架前台标签**——`shop.tres` pool_shelf 六行用 `kind_id` 指向 `adventure_table.gd:29` 的 CURIO_KINDS 常量(其中 feat_ticket 为显式豁免种、不入该词表,5/6 命中——`config_db_cross_validator.gd:109-110`),`display_name` 只是这层的前台名;买到的东西进**冒险加算区**、冒险结束清零。所以它们是**奇物属性件**,既不是武器也不是护甲,局内不改变任何锻造等级。命名上的隐患确凿:行名里的"锻"字与营地锻造同字,是误读的直接来源。**2026-10-12 用户裁决(8a 连带·C 项)**:三行按「属性 + 后缀」改名,后缀取 **祝福** ⇒ **攻击祝福 / 生命祝福 / 护甲祝福**(D&D 参照调研:Blessing 为"神赐恩惠"义;用户同时裁定——参考书目里出现过「祝福」一词不构成禁用)。**改名后这三条在语感上彻底脱离"锻造"一词,与营地工作间不再有字面关联**,而结构关系不变(仍是奇物「属性值提升」三轴,机制分类名不动,只改玩家向显示名)。**执行归独立改名小批**(待办清单在册)——**已于 2026-10-12 执行完毕并合入 master**(commit `0c93a71` / `0144f60`:shop.tres 六行锻升→祝福;reward.tres / status_effects.tres 护甲强化→盾牌加护;smoke_all.gd:1199 断言同步;注记段四处残留肃清;冒烟两跑 864 项 0 失败)。
- **局内不存在武器/护甲的提升(8a 裁连带核结)**:用户指出"局内没有可以提升武器的地方"——**属实**。查实:局内商店货架的三行(现役旧名"攻击锻升 / 生命锻升 / 护甲锻升"——**2026-10-12 已改名,见下**),**正是奇物「属性值提升」三轴(攻击 / 生命上限 / AC)的货架前台标签**——`shop.tres` pool_shelf 六行用种类字段指向冒险表的奇物种类词表(其中凭证行为显式豁免种、不入该词表,六行中五行命中),`display_name` 只是这层的前台名;买到的东西进**冒险加算区**、冒险结束清零。所以它们是**奇物属性件**,既不是武器也不是护甲,局内不改变任何锻造等级。命名上的隐患确凿:行名里的"锻"字与营地锻造同字,是误读的直接来源。**2026-10-12 用户裁决(8a 连带·C 项)**:三行按「属性 + 后缀」改名,后缀取 **祝福** ⇒ **攻击祝福 / 生命祝福 / 护甲祝福**(D&D 参照调研:Blessing 为"神赐恩惠"义;用户同时裁定——参考书目里出现过「祝福」一词不构成禁用)。**改名后这三条在语感上彻底脱离"锻造"一词,与营地工作间不再有字面关联**,而结构关系不变(仍是奇物「属性值提升」三轴,机制分类名不动,只改玩家向显示名)。**改名已于 2026-10-12 执行完毕并合入 master**(shop.tres 六行锻升→祝福;reward.tres / status_effects.tres 护甲强化→盾牌加护)。
- **专长联动**:现役专长均为跨攻击类型通用件(不按 attack_type 分系——既无 normal 条件件、也无 pierce/magic 条件件,`feats.tres` 29 行实证),武器类型差异只经克制矩阵的 matchup 行体现;「选某类型激活某系专长」在本项目专长体系下不成立(2026-10-12 v0.6 订正:原「选长枪激活穿刺系专长的价值」为无现役对应物的表述,删除)。现有死条件 `magic_vs_magic`(魔法打"魔法甲"——现行甲域无此型,永不可触发 = 废卡):**改值复活案已作废(2026-10-11 D 组核结——所属件魔网织者在素材库表二专长 27 件退役删除名单)**,该行随专长退役删行批整体消解,本系统零触碰(2026-10-12 订正:本节原写"推荐复活"与实装边界第 3 条结案口径冲突,随本批对齐)。「魔法打魔法护甲」的克制语义由矩阵九格承载(magic vs ward = −1),新专长框架如需同类件 = 彼批的新卡设计题。
- **天赋树**:同钥石经济;注入面分层叠加且**所有权切分**(2026-10-11 裁)——树脊柱只持生命/命中淬炼(树零攻防来源),攻/AC 唯一入口=本系统路线加值(只按当前选中武器/甲生效)。
- **冒险前资源配比预设批(挂起冻结中)**:本设计不预设该批的重启口径、不占用其任何冻结裁决、不改其冻结文本。该批重启时须显式核对两处交汇面:①预算包络是否把武器类型计入克制注;②容量柜台与锻造工坊工作间(同在营地)的资源竞争关系。
## 现役代码事实(设计所依据的实态,2026-10-07 复核)
| 事实 | 出处 |
|---|---|
| 局内商店货架三行 = 奇物「属性值提升」三轴的货架前台标签(攻击/生命/护甲祝福);营地属性锻造行族已随迭代 21 T5 退役,无"武器"实体 | `shop.tres:68-92`、`meta.tres:73-76` 退役登记 |
| 主角攻击类型轴现役值 normal(枚举 normal/pierce/magic)——默认值所有权随本设计移交锻造工坊配置、hero.tres 该轴随实装删除 | `hero.tres:11`、`hero_table.gd:13` |
| 主角甲型结构位现役值 light(域 light/ward/heavy);AC=10 现役生效——默认值所有权随本设计移交锻造工坊配置、hero.tres 该轴随实装删除 | `hero.tres:9-10` |
| 克制矩阵 3×3 九格满格(普通/穿刺/魔法 × 轻甲/魔法护甲/重甲),结构冻结 | `settlement.tres:31-47` |
| 敌方装甲分布:轻甲×14 / 重甲×4 / 魔法护甲×1(L2 Boss 蛛化卓尔精灵) | `enemy.tres` |
| 甲效矩阵 normal 行取值:轻甲 0 / 魔法护甲 +1 / 重甲 −1(整数点数:+1 被克 / 0 中性 / −1 挡住) | `settlement.tres:31-47` |
| 敌方攻击类型轴**未开**——单据 `attack_type` 栏现役为 `""`(空串恒不查矩阵,`""` 与"群伤/反噬/地形/敌方技"同列无类型族);`enemy.tres` 登记「敌方技能攻击类型轴仍未开(全部 normal 档)」 | `damage_controller.gd:19/:550-552`、`enemy.tres:527` |
| 效果引擎效果类型闭集 **31 种**(原记 24 系 2026-10-07 旧快照——迭代 20 T1 +7、迭代 22 T3 +1,2026-10-13 迭代 23 反哺写入订正;冒烟断言钉 31,将来加词即转红提示复核) | `effect_engine.gd` |
| 营地消费货币=钥石(现役入口 tree_buy / buy_attune_refresh → spend_keystone——buy_forge 随 forge_products 退役删除);GP=局内一次性货币,开局清零不入存档 | `meta_progression.gd:175/:354`、`adventure_runner.gd:100` |
## 实装边界(执行归实装批,本文只登记方案)
1. **锻造路线行族(新建)**:现役 `meta.tres` forge_products 行族已随迭代 21 T5 攻防所有权切分整体退役(`meta.tres:73-76`、`meta_table.gd:15-16`、`meta_progression.gd:33-35` 三处退役登记,buy_forge/forge_price 访问器随删)——武器/护甲路线为**新建行族**,行族命名、字段结构与落表位置归实装批定义,本文只登记内容口径(三武器/三甲各自等级、定价、解锁通道,见「工作间内容清单」);生命项不在本批(生命强化唯一入口=树脊柱生命线,2026-10-11 A① 裁)。同表 boss_keystone / workshop_items / conversion / attune_refresh_upgrade 四行族零触碰。**实态回填(2026-10-13 迭代 23 实装兑现)**:落表=新表 `data/tables/forge.tres`(ForgeTable:默认两轴常驻持有 + 六路线行族,`meta.tres` 三行族零触碰);路线 id = sword/spear/staff/light/heavy/ward(甲路线 id=甲型值本身);字段闭集 12 项;访问器 = `ConfigDB.forge_*` 六个(迭代 23 T1/T2 交付)。
2. **两根类型轴的所有权移交**:`attack_type` / `armor_type` 的默认值由锻造工坊配置常驻持有,hero.tres 静态表两轴**删除**;营地选中态写存档域,冒险开始经参数面注入(与现役「表运行期只读、永久强化写存档」纪律一致——`meta_progression.gd:12-13/119`;原「写 hero.tres 休眠轴」口径随 v0.6 订正,见「注入衔接」节)。
3. **`feats.tres` magic_vs_magic 死行**:复活案作废(2026-10-11 核结——所属件魔网织者已在素材库表二专长 27 件退役删除名单),随专长退役删行批整体消解,本批零触碰;「魔法打魔法护甲」克制语义由矩阵九格承载,新专长框架如需同类件=彼批新卡设计题。
4. **存档域**:新增三武器等级 + 三甲等级 + 当前选中标记(各 1 个);**不设特效域**(6 裁)。旧档按开发阶段无迁移红线破坏性废弃(2026-10-11 用户裁,禁迁移/折算)。**已于 2026-10-12 v0.6 完备性检查批预登记**(存档序列化.md 域清单表 + 版本沿革 v7 计划行——用户令文档批次即登记、不等实装批)。
5. **满级特效:本期不实装**(6 裁)——不开三条新参数位(暴击面 / 折扣档 / 护盾量),不加效果类型词,扩展位留空。
6. **敌方攻击类型轴填值(3b 裁连带,跨系统汇合点)**:敌方攻击单据 `attack_type` 按「未设置 = 默认 normal」填值,使其参与克制矩阵查表(现役为 `""` 恒不查)。**执行面归敌人系统 / 战斗结算侧**,锻造工坊实装批只登记该需求与其数值影响——该改动会全局平移主角受伤点数(穿重甲 −1、穿魔法护甲 +1),属数值敏感变更,须纳入第 8 条的配对读数验证。敌方 attack_type 是敌方定义数据(enemy.tres / skills.tres 行级,与敌种名字/血量同批同时读写、无独立时序),填值归敌人系统/战斗结算侧自然推进——本批只按 3b 裁落结构面(矩阵查表现役已支持),与锻造工坊批期无耦合(2026-10-12 订正:原「两批排期协商(可同批做,也可先行)」句删除——用户裁:时序问题不存在)。
7. **术语登记**:三型甲统一命名——**只有「魔法护甲」(「魔法甲」系笔误,不登记为别名)**;连同工作间两区定名(武器锻造区 / 护甲锻造区)顺带登记 `design/gdd/术语表.md`。
8. **数值验证义务**:实装批必须带改前/改后同随机种子配对读数验证难度影响;统一调整归 Alpha 收官 M4 数值批。
## 裁决结果清单(17 裁点全表·2026-10-12 清零)
17 裁点全部拍板,本表即终态(含本会话呈报的两条子问 4a-1 / 4b-1,共 19 行)。「推翻推荐」四行已同步改正文对应节。实装批以本表 + 下节「工作间内容清单」为需求卡片输入。
17 裁点全部拍板,本表即终态(含本会话呈报的两条子问 4a-1 / 4b-1,共 19 行)。「推翻推荐」四行已同步改正文对应节。
| # | 裁点 | 裁决结果 | 写入处 | 备注 |
|---|---|---|---|---|
@@ -182,7 +155,7 @@
| 5b | 货币 | 纯钥石(用户注:当前无其他材料,钥石 = 统一货币;后续若要区分再重构,不预先建面) | 「经济」节 | 按推荐案(且受两币分工条款锁定) |
| 5c | 材料经济 | 纯钥石零材料 | 「经济」节 | 按推荐案 |
| 6 | 满级特效深度 | **本期不做,只留扩展位**——不设计清单 / 不定数值 / 不实装 / 不开参数位 | 「满级特效扩展位」节 | **推翻推荐案**(原 = 复用为主 + 三条新参数位) |
| 8a | 货架现名 → 实裁 = 工作间命名(区名 + 路线行名)+ 局内核结 | **区名**:武器锻造区 / 护甲锻造区;**路线行名**统一「件名 + 锻造」= 长剑锻造 / 长枪锻造 / 法杖锻造、轻甲锻造 / 重甲锻造 / 魔法护甲锻造(第一件武器显示名 剑 → **长剑**)。**局内核结**:局内不存在武器 / 护甲的提升——商店货架三行「攻击锻升 / 生命锻升 / 护甲锻升」即奇物「属性值提升」三轴的货架前台标签(`shop.tres` pool_shelf kind_id → `adventure.tres` CURIO_KINDS),与锻造无关;是否去"锻"字归实装批呈现面 | 「与相邻系统的边界·工作间两区定名 / 局内不存在武器提升」 | 裁点重定向 + Y 层命名 |
| 8a | 货架现名 → 实裁 = 工作间命名(区名 + 路线行名)+ 局内核结 | **区名**:武器锻造区 / 护甲锻造区;**路线行名**统一「件名 + 锻造」= 长剑锻造 / 长枪锻造 / 法杖锻造、轻甲锻造 / 重甲锻造 / 魔法护甲锻造(第一件武器显示名 剑 → **长剑**)。**局内核结**:局内不存在武器 / 护甲的提升——商店货架三行「攻击锻升 / 生命锻升 / 护甲锻升」即奇物「属性值提升」三轴的货架前台标签(`shop.tres` pool_shelf 种类字段 → `adventure.tres` 奇物种类词表),与锻造无关;是否去"锻"字归实装批呈现面 | 「与相邻系统的边界·工作间两区定名 / 局内不存在武器提升」 | 裁点重定向 + Y 层命名 |
## 工作间内容清单(实装批需求卡片输入)
@@ -229,7 +202,7 @@
- 入战 AC 三层可加:甲型基础 AC + 甲路线等级加值 + 局内加算区累加。
### 8 · 存档域
三武器等级 + 三甲等级 + 当前武器标记 + 当前甲标记;**无特效域**。旧档破坏性废弃(禁迁移 / 折算)。**已于 2026-10-12 预登记存档序列化.md**(域清单 + 版本沿革 v7 计划行)。
三武器等级 + 三甲等级 + 当前武器标记 + 当前甲标记;**无特效域**。旧档破坏性废弃(禁迁移 / 折算)。存档域见[存档序列化](../表现与支撑/存档序列化.md)(域清单与版本沿革 v7 行)。
### 9 · 本期明确不做
① 满级特效(清单 / 数值 / 参数位 / 存档域全不做);② 敌方攻击类型轴填值——跨系统需求,执行面在敌人系统 / 战斗结算侧,本批只登记。
+83 -87
View File
@@ -1,10 +1,10 @@
# UX Spec: 战斗屏(Battle Screen)
> **Status**: Complete — /ux-review 修订落稿(2026-09-12,blocking 7 项已修,待复检)
> **Status**: Complete(现行)
> **Author**: user + ux-designer
> **Last Updated**: 2026-10-26(迭代 38 卡⑦ 调试入口发布态恒隐:DS_DEBUG 门控表述换代——见元素表「调试浮窗」行与「调试变体」行)
> **Last Updated**: 2026-10-29
> **Platform Target**: PC (Steam)——Keyboard/Mouse 主(全部操作纯鼠标可完成)、Gamepad Partial 非 MVP、Touch None(同 technical-preferences)
> **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算 `refresh_board` 编排 + 恒 same_board、registry `rap_rules` 同步——见 OQ11)
> **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算的挖掘期刷新编排 + 恒同盘不变、系统索引的刷新规则同步——见 OQ11)
> **Template**: UX Spec
> **覆盖子屏**: 挖掘阶段主界面 · 轮末自动战斗结算(战斗结算明细屏) · 胜利/失败终局 · ~~结算明细回看~~(2026-10-04 P-C7 裁:冒险中回看退役归「生涯」——待办清单登记)
> **输入义务**: 排雷玩法×6 + 行动经济 + 战斗结算×3 + 敌人系统×4 + 增益状态×4 + 地图战斗资源×5(旧版 systems-index 门禁增补记录,缺项即 blocking——承接落点见 §13 义务对照表)
@@ -28,7 +28,7 @@
## Player Context on Arrival
- **首次到达**:从关卡首节点(战×2 的第一战)进入。MVP 无引导流,chord 肌肉记忆提示等非模态提示承担首次教学(见 §States)
- **到达前刚在做**:关卡画面(独立屏)上点击战斗节点(LevelRunner 推进);或上一场战斗胜利后返回关卡画面再选下一节点
- **到达前刚在做**:关卡画面(独立屏)上点击战斗节点(关卡推进方受理);或上一场战斗胜利后返回关卡画面再选下一节点
- **情绪状态**:棋手式清明专注(art-bible §2.1,能量 2/10)——挖掘期画面必须无杂音;结算期转为清算爽感(7/10)
- **到达方式**:游戏推进送入(非自愿导航),进入即处于挖掘阶段(棋盘已 install、初始 3×3 已展开、小人出生)
@@ -41,14 +41,14 @@
```
存档 → 冒险 → 关卡(关卡画面 = 节点图选路常驻枢纽) → 【战斗屏】
├─ 胜利 → 返回关卡画面选下一节点(落地奖励/恢复节点时开节点覆盖层确认 → 下一场战斗屏)
├─ 胜利(末节点 Boss)→ 关卡结算(level_clear,spec:[level-clear.md](level-clear.md))→ 下关
├─ 胜利(末节点 Boss)→ 关卡结算(关卡完成,spec:[level-clear.md](level-clear.md))→ 下关
└─ 失败 → 冒险落幕 → 返回存档/营地
```
战斗屏是**上下文相关屏**(仅由战斗节点进入),非顶层常驻。关卡内节点进度常驻关卡画面(独立屏的常驻枢纽形态,节点态即进度呈现:已过暗金、当前金、候选通道亮金、Boss 紫赤描边),战斗屏零进度承载——战斗结束返回关卡画面,玩家始终知道自己身处图中第几环。
- 上游屏:关卡画面(选路枢纽,屏级 spec:[level-map.md](level-map.md)——2026-09-27 建档;分支节点图形态,切片第一关为单链特例)
- 下游屏:关卡画面(选路枢纽,spec:[level-map.md](level-map.md))——战斗胜利后返回选路;落地奖励/恢复节点时由关卡画面域开节点覆盖层(NodeOverlay,恢复点全量服务、奖励点=三选一构筑点)确认兑付——节点奖励屏 spec 已出生:[node-reward.md](node-reward.md) v1.0(2026-09-29 迭代 7 T5 顺带出生,待办清单条目兑现;2026-09-27「不立独立 spec」拍板随之被取代)
- 下游屏:关卡画面(选路枢纽,spec:[level-map.md](level-map.md))——战斗胜利后返回选路;落地奖励/恢复节点时由关卡画面域开节点覆盖层(恢复点全量服务、奖励点=三选一构筑点)确认兑付——节点奖励屏 spec 已出生:[node-reward.md](node-reward.md) v1.0(2026-09-29 迭代 7 T5 顺带出生,待办清单条目兑现;2026-09-27「不立独立 spec」拍板随之被取代)
- 下游屏:关卡结算([level-clear.md](level-clear.md),末节点 Boss 胜利——2026-09-27 建档)
- 平行屏:图例面板 overlay(本屏内域屏);~~结算明细回看 overlay~~(2026-10-04 P-C7 退役——冒险中回看归「生涯」)
@@ -60,16 +60,16 @@
| Entry Source | Trigger | Player carries this context |
|---|---|---|
| 关卡战斗节点 | 点击节点(LevelRunner 推进 encounter) | 携带 HP(跨战斗延续)、RAP=0(战斗域新开) |
| 关卡战斗节点 | 点击节点(关卡推进方受理本场遭遇) | 携带 HP(跨战斗延续)、RAP=0(战斗域新开) |
| 上一场战斗胜利 → 返回关卡画面 → 选下一节点 | 同上 | 同上 |
**离开**:
| Exit Destination | Trigger | Notes |
|---|---|---|
| 关卡画面(选路枢纽) | 敌方全灭(战斗结算判定段 verdict=win)且非末节点 | 不可逆:battle_over 已发生,RAP 清零;落地奖励/恢复节点时开节点覆盖层确认(NodeOverlay,关卡画面域) |
| 关卡结算 | Boss 战胜利 → level_clear(spec:[level-clear.md](level-clear.md)) | 不可逆:~~关卡收束清库义务载体 = 秘宝背包~~(秘宝过渡形态〔关卡域库存/局内两步使用〕已随迭代 11 第①批退役——现行形态 = 营地同调装配、整局冒险生效,无关卡域清库面〔旧关卡域库存 treasure_inventory 为投放渠道全退役后恒空的机械惰性承载,拆线归实装批——见战斗结算「轮次生命周期」节注〕,见秘宝.md) |
| 冒险落幕 | 主角 HP=0(verdict=lose) | 不可逆:Roguelike 死亡重开(art-bible §2.5 温柔惋惜调性) |
| 关卡画面(选路枢纽) | 敌方全灭(战斗结算判定段判胜)且非末节点 | 不可逆:战斗结束已发生,RAP 清零;落地奖励/恢复节点时开节点覆盖层确认(关卡画面域) |
| 关卡结算 | Boss 战胜利 → 关卡完成(spec:[level-clear.md](level-clear.md)) | 不可逆:~~关卡收束清库义务载体 = 秘宝背包~~(秘宝过渡形态〔关卡域库存/局内两步使用〕已随迭代 11 第①批退役——现行形态 = 营地同调装配、整局冒险生效,无关卡域清库面〔旧关卡域库存 treasure_inventory 为投放渠道全退役后恒空的机械惰性承载,拆线归实装批——见战斗结算「轮次生命周期」节注〕,见秘宝.md) |
| 冒险落幕 | 主角 HP=0(判负) | 不可逆:Roguelike 死亡重开(art-bible §2.5 温柔惋惜调性) |
| 无中途退出 | — | MVP 不设战斗内放弃/暂停菜单(回合制无时间压力;如需暂停留 Playtest 诊断按钮,调试用) |
---
@@ -115,17 +115,17 @@
### 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 实时流);顶部中央轮次·阶段指示 |
| **C 角色信息** | 左下 240px × 530px(2026-10-06 方案二:548→530) | 攻击值(含修正显示)、护甲 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 槽(上缘内侧) |
| **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 角色信息** | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 攻击值(含修正显示)、护甲 AC;秘宝槽、奇物列表(现行标题「特性」)——HP 条/RAP 池/状态效果槽/立绘占位/钱包行退役(2026-09-26 信息归属拍板:HP 与状态唯一展示面 = B 区站桩;RAP 迁 D 区 AP 条;GP/钥石迁出战斗屏) |
| **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)。
> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;registry terminology 与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;封印特效未实装,占位 = 封印黑「封」字形(board_view 程序化占位,现行代码仍为旧「旗」字形、随代码术语批换代,贴图在位=字形退役;旗朱→封印紫 2026-09-26→封印黑 2026-09-28 用户拍板)。
> 呈现术语注记(2026-09-25):封印 = 正式动作名(全库统一;登记册术语与术语表 v1.13;印记 = 封印印记,art-bible §3.2)——机制与键名不变;封印特效未实装,占位 = 封印黑「封」字形(棋盘视图程序化占位,现行代码仍为旧「旗」字形、随代码术语批换代,贴图在位=字形退役;旗朱→封印紫 2026-09-26→封印黑 2026-09-28 用户拍板)。
> 可挖域微光画法定稿(2026-09-25 用户拍板):**接触带·空间侧 + 蓝族**——光画英雄格周界接触面(正交邻墙=整边,斜向邻墙=角点+两半边),详见 art-bible §3.2;现行墙格整框绿相呼吸为待退役占位,实现切换归表现批(探针断言顺带)。
@@ -145,33 +145,29 @@
- N 的取值与每关板型的对应关系读[棋盘生成](../systems/排雷循环/棋盘生成.md),现行值的事实源是 `data/tables/board_gen.tres`。
- 数字字号 = 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
| 区 | 组件 | 类型 | 内容 | 交互 | 现有 demo 组件 |
| 区 | 组件 | 类型 | 内容 | 交互 | 载体 |
|---|---|---|---|---|---|
| B | 主角站桩立绘 | 站桩(无框立绘+落影) | HP 条落底盘(2026-10-06 方案二拍板:站桩排式 = **立绘→血条→状态**,全场血条对齐一线;血条嵌石板位、台缘线留作地面线;原「悬浮头顶」退役——高盒下血条-立绘空洞,用户点名否决)、状态效果小图标一排挂血条正下方(形状+色相双通道、层数角标;空态不占位退化为空行)——**HP 与状态效果的唯一展示面(2026-09-26 信息归属拍板,原 C 区状态槽职责〔悬停 tooltip 来源+语义+剩余结算次数〕并入本组件)**;攻击值等规划数据仍归 C 区数据框(2026-09-20 全站桩拍板,英雄卡退役) | 悬停状态小图标看 tooltip | HUDView 拆分改造 |
| B | 主角站桩立绘 | 站桩(无框立绘+落影) | HP 条落底盘(2026-10-06 方案二拍板:站桩排式 = **立绘→血条→状态**,全场血条对齐一线;血条嵌石板位、台缘线留作地面线;原「悬浮头顶」退役——高盒下血条-立绘空洞,用户点名否决)、状态效果小图标一排挂血条正下方(形状+色相双通道、层数角标;空态不占位退化为空行)——**HP 与状态效果的唯一展示面(2026-09-26 信息归属拍板,原 C 区状态槽职责〔悬停 tooltip 来源+语义+剩余结算次数〕并入本组件)**;攻击值等规划数据仍归 C 区数据框(2026-09-20 全站桩拍板,英雄卡退役) | 悬停状态小图标看 tooltip | HUD 视图拆分改造 |
| B | 敌人站桩立绘 ×1–3 | 站桩(无框立绘+落影) | 血条+顺位徽标落底盘(同主角排式,2026-10-06 方案二;**大体档〔显示 >150px 级:drider/duergar_king〕允许向上溢出盒顶 N px(N 由实装批 C0 实算,与背景/光柱无关——现行溢出区为临时光柱氛围层,正式舞台背景插画到位后该层退役,判定不变),血条仍落底**)、状态小图标挂血条正下方(敌方 side-wide 增益各立绘同值呈现——蓄力/硬化/削弱/暴走/警觉;伏击层数通道随 2026-09-20 八型批退役——数据源 = 战斗结算流水经增益状态投影,敌人系统×4④;2026-09-16 敌人技能池批改型:行为环点拆除〔proposal §2.4〕,意图占位 = 本拍摇定结果的动画占位——普通攻击零表示 / 特殊技类型级提示,**意图徽标悬浮头顶上方,探顶档压胸**,敌方增益〔蓄力/硬化〕入状态图标行);死亡灰化加碎裂退场直接演在立绘上(2026-09-20 全站桩拍板,敌人卡退役);血条下名字行 = 纯名字 + 护甲类型图标(形状+色相双通道,与状态图标同行共存,状态空时为纯名字;原「底座下」随石板退位改落底排),AC 数值不展示——2026-09-26 终裁(用户口径:HP 信息已足够,攻防数值不直接写上;护甲类型图标保留克制对策可读性——类型可读、数值隐藏) | 只读 | 新建 |
| B | 轮次·阶段指示 | 标签 | "第 N 轮 · 挖掘中/结算中"(终局定格「战斗结束」;Boss 变体附 Boss 名) | 只读 | 新建 |
| B | 结算明细带 | 滚动列表 | 结算期逐行流水([战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 9 类行协议);挖掘期收起为上轮摘要 1 行 | 点击展开明细回看 overlay(2026-10-04 P-C7 退役——带保留单行摘要,回看归「生涯」) | battle_log_view.gd 改造 |
| B | ~~结算明细回看~~(已退役) | —— | 2026-10-04 P-C7 裁:冒险中回看退役归「生涯」——settlement_review_overlay.gd 为未接线遗留件(待办清单登记) | —— | —— |
| C | 攻击数据框 | 直角细线数据框 | 攻击值 + 修正来源角标 + 护甲 AC(AC 二轮拍板 2026-09-26 补入——我方 AC 唯一展示面;原「HP/攻击/RAP 显示」——HP 与 RAP 随信息归属拍板迁出本组件) | 只读 | HUDView 拆分 |
| C | 秘宝槽 | 图标槽 ×4 | 四槽壳在役、空槽占位(标题「秘宝」,悬停「秘宝槽(空)」);局内两步使用与关卡域库存随秘宝过渡形态退役——现行秘宝 = 营地同调装配件(秘宝.md),本区展示重接归装配面批次 | 悬停 tooltip | hero_info_panel_view.gd |
| C | 奇物列表(现行标题「特性」) | 列表/图标行 | 已实装(迭代 9 卡⑫ 接线、迭代 12 呈现补齐;2026-10-12 呈现修复批整改——逐件行「件名 ×N」,零内部分类字样);锻造/大奖励/关卡奖励节点获得的属性提升——中循环数据,因改变战斗数学且无其他展示面准入战斗屏;与战斗内状态效果(B 区站桩)分区,防「战斗结束被清」误读 | 只读 | hero_info_panel_view.gd |
| D | AP 条 | 点阵 + 按钮组 | 本轮 AP 点阵、停手按钮(左)、**RAP 点阵+数字(自 C 区迁入 2026-09-26——RAP 唯一消费口 = 弃图刷新,资源数字贴消费按钮;RAP 清零可见反馈〔行动经济〕载体随迁)**、弃图刷新按钮(右端·雷图右上角;RAP−8 · 本轮剩 N 次——上限 2 次已随行动经济 2026-09-12 修订生效) | 点击提交意图 | HUDView 拆分 |
| D | BoardView | 网格 | 棋盘全部格态 + 小人 + 封印印记 + 变异纹带 + 奖励身份 icon(已开格;绘制优先级 数字>小人>封印印记>奖励徽章>变异纹带>底色——徽章不进数字区,样张验收项) | 左/右键意图提交 | ✅ board_view.gd 迁移 |
| B | 结算明细带 | 滚动列表 | 结算期逐行流水([战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 9 类行协议);挖掘期收起为上轮摘要 1 行 | 点击展开明细回看 overlay(2026-10-04 P-C7 退役——带保留单行摘要,回看归「生涯」) | 改造 |
| B | ~~结算明细回看~~(已退役) | —— | 2026-10-04 P-C7 裁:冒险中回看退役归「生涯」——回看覆盖层为未接线遗留件(待办清单登记) | —— | 未接线 |
| C | 攻击数据框 | 直角细线数据框 | 攻击值 + 修正来源角标 + 护甲 AC(AC 二轮拍板 2026-09-26 补入——我方 AC 唯一展示面;原「HP/攻击/RAP 显示」——HP 与 RAP 随信息归属拍板迁出本组件) | 只读 | HUD 视图拆分 |
| C | 秘宝槽 | 图标槽 ×4 | 四槽壳在役、空槽占位(标题「秘宝」,悬停「秘宝槽(空)」);局内两步使用与关卡域库存随秘宝过渡形态退役——现行秘宝 = 营地同调装配件(秘宝.md),本区展示重接归装配面批次 | 悬停 tooltip | 已实装 |
| C | 奇物列表(现行标题「特性」) | 列表/图标行 | 已实装(迭代 9 卡⑫ 接线、迭代 12 呈现补齐;2026-10-12 呈现修复批整改——逐件行「件名 ×N」,零内部分类字样);锻造/大奖励/关卡奖励节点获得的属性提升——中循环数据,因改变战斗数学且无其他展示面准入战斗屏;与战斗内状态效果(B 区站桩)分区,防「战斗结束被清」误读 | 只读 | 已实装 |
| D | AP 条 | 点阵 + 按钮组 | 本轮 AP 点阵、停手按钮(左)、**RAP 点阵+数字(自 C 区迁入 2026-09-26——RAP 唯一消费口 = 弃图刷新,资源数字贴消费按钮;RAP 清零可见反馈〔行动经济〕载体随迁)**、弃图刷新按钮(右端·雷图右上角;RAP−8 · 本轮剩 N 次——上限 2 次已随行动经济 2026-09-12 修订生效) | 点击提交意图 | HUD 视图拆分 |
| D | 棋盘视图 | 网格 | 棋盘全部格态 + 小人 + 封印印记 + 变异纹带 + 奖励身份 icon(已开格;绘制优先级 数字>小人>封印印记>奖励徽章>变异纹带>底色——徽章不进数字区,样张验收项) | 左/右键意图提交 | ✅ 已实装 |
| D | 悬停预承诺光标 | 光标态 | 移动/挖/封印三语义光标 + 目标格边框高亮 | 自动 | 新建(排雷玩法×6③) |
| D | 可挖域指示 | 常驻微高亮 | 小人 8 向相邻可操作格 | 自动 | 新建(排雷玩法×6③) |
| D | 移动路径预览 | 路径高亮 | 悬停已开格显示 BFS 路径(合法性指示) | 自动 | 新建(排雷玩法×6⑤,裁决见下) |
| D | toast 槽 | 非模态提示 | 拒绝原因(双源)、voided、满层溢出、chord 提示、零合法板告知 | 自动消退 2.5s | 新建 |
| D | 图例按钮 + 面板 | 按钮+模态 overlay | 格态/变异语义图例 | 开/关 | 新建 |
| 全屏 | 胜利/失败 overlay | 模态 | 金蜡封/暮蓝收印 + 判定行(明细回看入口已随 P-C7 退役——回看归「生涯」) | 继续/下一节点 | battle_end_overlay.gd |
| 调试 | 调试浮窗 | 调试面板 | 种子/数值调整/批量模拟/两层调试入口(发布态恒隐,无 UI 开启口——写死行已随迭代 38 卡⑦ 删除,DS_DEBUG 门控口径见「调试变体」行;TunePanel 已随迭代 15 卡①吸收退役) | 调试 | debug_overlay.gd |
| 全屏 | 胜利/失败 overlay | 模态 | 金蜡封/暮蓝收印 + 判定行(明细回看入口已随 P-C7 退役——回看归「生涯」) | 继续/下一节点 | 已实装 |
| 调试 | 调试浮窗 | 调试面板 | 种子/数值调整/批量模拟/两层调试入口(发布态恒隐,无 UI 开启口——写死行已随迭代 38 卡⑦ 删除,调试开关门控口径见「调试变体」行;调参面板已随迭代 15 卡①吸收退役) | 调试 | 调试浮窗 |
**布局决策 2 —(已删除,编号留空)**:原「窗口形态 = 右栏原位激活」决策随可选类道具整体下线(2026-09-15 格面资源批)。编号留空,免布局决策 3/4 的既有交叉引用失锚。
@@ -179,10 +175,10 @@
- 备选(登记不采纳):穿行动画轨道——移动免费高频,动画时间税违反 P4 点击摩擦护栏(每轮输入动作:AP ≤2 的观测假设);正式版若 playtest 反馈"移动太瞬"再议(留痕)
**布局决策 4 — 弃图刷新 = 挖掘期当场换图(2026-09-12 用户拍板,同日落稿上游)**:按钮置 AP 条右端(雷图右上角)。点击即当轮就地换新图继续挖——AP 不重置、剩余 AP 保留,RAP−8,本轮上限 2 次。
- **上游修订(2026-09-12 已落稿,OQ11)**:① 行动经济 `request_refresh` Active 相位受理 + 每轮上限 2 次(Waiting 代理退役)② 排雷玩法装板受理域增 Active·仅刷新路径 ③ 编排 owner = **战斗结算 `refresh_board`**(行动经济扣费 → 排雷玩法 retire → 棋盘生成 generate → 排雷玩法 install → 地图战斗资源 roll_board 恰一次;无结算段、无 round_end——战斗结算编排唯一性原则保持)④ 旧板封印印记随旧板**作废**(零兑付、封印 AP 不返还——排雷玩法 EC11 全量重置承载;确认微交互须显式提示)。**经济预警**:当场刷新无结算发生,原「敌方时间税」自惩失效——行动经济 OQ3 弃图率/刷新率预警阈值联动复核。
- **上游修订(2026-09-12 已落稿,OQ11)**:① 行动经济 Active 相位受理刷新意图 + 每轮上限 2 次(Waiting 代理退役)② 排雷玩法装板受理域增 Active·仅刷新路径 ③ 编排 owner = **战斗结算的挖掘期刷新编排**(行动经济扣费 → 排雷玩法退役旧板 → 棋盘生成出新图 → 排雷玩法装板 → 地图战斗资源摇奖励恰一次;无结算段、无轮次结束——战斗结算编排唯一性原则保持)④ 旧板封印印记随旧板**作废**(零兑付、封印 AP 不返还——排雷玩法 EC11 全量重置承载;确认微交互须显式提示)。**经济预警**:当场刷新无结算发生,原「敌方时间税」自惩失效——行动经济 OQ3 弃图率/刷新率预警阈值联动复核。
- 备选(已否决):结算期决策提示(Waiting 内联)——结算期不做刷新决策(用户拍板);挖掘期预承诺 stop+reroll 复合——决策即终轮,不符「换图继续挖」语义。
**敌人意图线索映射目录(敌人系统×4① 成文占位,2026-09-12;2026-09-16 敌人技能池批改型注记:行为环三态〔攻击/蓄力/重击〕预告随行为环退役——现行数据面 = 敌人系统三档可见性规格〔普攻零表示 / 特殊技类型级动画〔攻击系/增益系两型〕/ 暴击掷点层无前置〕,池与权重不公开;下表 grub/ambusher 三态清单留档作废,终稿资产目录随战斗表现样张批重立;**命名双轨完成注记 2026-10-06**:grub/ambusher 两键本身已随敌人系统八型批退役——现行键集 = 八敌 registry〔goblin/bugbear/ogre_chief/giant_spider/drider/duergar/hook_horror/duergar_king〕,本表键名仅为历史留痕、禁止作为现行 enemy_id 引用)**:
**敌人意图线索映射目录(敌人系统×4① 成文占位,2026-09-12;2026-09-16 敌人技能池批改型注记:行为环三态〔攻击/蓄力/重击〕预告随行为环退役——现行数据面 = 敌人系统三档可见性规格〔普攻零表示 / 特殊技类型级动画〔攻击系/增益系两型〕/ 暴击掷点层无前置〕,池与权重不公开;下表 grub/ambusher 三态清单留档作废,终稿资产目录随战斗表现样张批重立;**命名双轨完成注记 2026-10-06**:grub/ambusher 两键本身已随敌人系统八型批退役——现行键集 = 八敌登记表〔goblin/bugbear/ogre_chief/giant_spider/drider/duergar/hook_horror/duergar_king〕,本表键名仅为历史留痕、禁止作为现行敌种 id 引用)**:
| enemy_id | 视觉状态(意图线索) | 强度档位编码 | 环长编码 |
|---|---|---|---|
@@ -196,12 +192,13 @@
### ASCII Wireframe
> **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 历史。
```
1280 × 800 ────────────────────────────────────────────────────────────────────
┌─[B] 战斗画面 270px ──────────────────────────────────────────────────────────┐
┌─[B] 战斗画面(高读布局表) ──────────────────────────────────────────────────┐
│ /小人\ 第 3 轮 · 挖掘中 伏×2 伏×2 │
│ (落影) [ 兑现演出区 ] /蛆虫\ /蛆虫\ │
│ 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/)
│ └────────────────────────────────────────────────────────────────────────┘ │
├─[C] 角色信息 240px ───┬─[D] 扫雷图 ~1036px ──────────────────────────────────┤
│ 548px │ 548px │
├─[C] 角色信息 ─────────┬─[D] 扫雷图 ───────────────────────────────────────────┤
│ ┌──────────────────┐ │ ┌─────────────────────────────────────────────────┐ │
│ │ 攻击 6 (+50%) │ │ │ AP ●●●●●○○○ 5/8 RAP ▓▓▓░ 3/8 │ │
│ │ 护甲 AC 12 │ │ │ [⏹停手] [?图例] [↻刷新 8|剩2] │ │
@@ -232,19 +228,19 @@
| State / Variant | Trigger | What Changes |
|---|---|---|
| **挖掘阶段(默认态)** | 轮次开始 / 结算完毕回 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 |
| **完美封印高光** | 战斗结算高光判定(全图标中/零误标类) | 全屏喝彩级反馈(§2.2 最高档):金光带加倍扫射 + 「完美封印 ×2」横幅——P2 爆发兑现节点(创意风险 #2 落点) |
| **封印标中高光** | flag_judged{hit=true} | 该印记化暴击弹道加重 + 明细行金框——chord 缺席的即时兑现补偿(创意风险 #3 落点) |
| **封印标中高光** | 封印判定·标中 | 该印记化暴击弹道加重 + 明细行金框——chord 缺席的即时兑现补偿(创意风险 #3 落点) |
| **敌人死亡呈现** | 敌 hp=0(流水行击杀标记) | 该敌人立绘血条归零 → 立绘灰化 + 碎裂退场(**禁瞬消**——敌人系统资产下限「死亡瞬间须可辨识、禁止直接消失」,同等 blocking);战斗段短路的重击被掐灭不产行动明细行,由行序可推断(战斗结算);动画密度归战斗表现,呈现语义在本 spec |
| **胜利终局** | verdict=win | 金蜡封落于**扫雷图区中央**(§2.4 字面锚定),全屏暖白金 overlay;判定行 + 「下一节点」;明细回看入口;状态图标行全清可见反馈(增益状态×4④——B 区主角站桩图标逐枚化光收回 + 微文案「状态随战斗结束消散」,防「我的增益哪去了」误读) |
| **失败终局** | verdict=lose(hero hp=0,战斗段短路) | 明度不变的暮蓝 #5A6C8C 氛围 overlay(全作唯一暮蓝档);小人坐砖掸灰、落下的封印印记逐枚化光点收回(§2.5);判定行 +「返回」(明细回看已随 P-C7 退役——回看归「生涯」);状态图标行全清可见反馈(增益状态×4④,同胜利) |
| **胜利终局** | 判定胜利 | 金蜡封落于**扫雷图区中央**(§2.4 字面锚定),全屏暖白金 overlay;判定行 + 「下一节点」;明细回看入口;状态图标行全清可见反馈(增益状态×4④——B 区主角站桩图标逐枚化光收回 + 微文案「状态随战斗结束消散」,防「我的增益哪去了」误读) |
| **失败终局** | 判定失败(主角生命归零,战斗段短路) | 明度不变的暮蓝 #5A6C8C 氛围 overlay(全作唯一暮蓝档);小人坐砖掸灰、落下的封印印记逐枚化光点收回(§2.5);判定行 +「返回」(明细回看已随 P-C7 退役——回看归「生涯」);状态图标行全清可见反馈(增益状态×4④,同胜利) |
| **Boss 遭遇变体** | encounter=boss(遗迹守卫) | 战斗开始一次性演出:紫赤 #8F2F6A 影子爬过棋盘(§2.6);敌人立绘体量最大+锐角剪影;阶段指示附 Boss 名 |
| **空态:无状态效果** | 增益状态 EC12 | 状态小图标行隐藏(B 区主角站桩血条下图标行零图标时不占位;落底排〔2026-10-06 方案二〕后该行退化为名字行——主角无名字行则整行空置);原「C 区状态槽隐藏、HP/攻击/RAP 上移补位」裁决随 C 区槽退役(2026-09-26 信息归属拍板)作废;图例面板载空态说明(增益状态×① 成文项) |
| **调试变体** | 测试模式 | 调试浮窗入口发布态恒隐(`_debug_entry` 写死行已删,无 UI 开启口;开发期临时启用=本地改该字段缺省、不提交——TunePanel 已随迭代 15 卡①退役);Playtest 诊断按钮(卡死转储) |
| **调试变体** | 测试模式 | 调试浮窗入口发布态恒隐(调试入口开关为写死行、已删,无 UI 开启口;开发期临时启用=本地改该字段缺省、不提交——调参面板已随迭代 15 卡①退役);Playtest 诊断按钮(卡死转储) |
**拒绝/作废子态**(排雷玩法×6⑥、行动经济双源——每次拒绝玩家可感知):
- 棋盘面拒绝(sub_reason:非相邻/挖封印格等)→ 目标格 危险红 #A8321F 边框闪 + toast 文案(拒绝语义红族——旗朱已退役归封印色〔现行封印黑〕)(图标+文案冗余,色弱清单 #7)
- 棋盘面拒绝(细分原因:非相邻/挖封印格等)→ 目标格 危险红 #A8321F 边框闪 + toast 文案(拒绝语义红族——旗朱已退役归封印色〔现行封印黑〕)(图标+文案冗余,色弱清单 #7)
- 资源面拒绝(AP 不足/RAP 不足)→ AP/RAP 数字脉冲 + toast「AP 不足 · 剩 0 点」
- voided(stop 被作废等)→ toast「本轮已清图,停手未执行 · 剩余 AP 已作废」——防静默改判读感欺骗
@@ -256,15 +252,15 @@
| 组件 | 动作 | 输入 | 即时反馈 | 结果(经组合根) |
|---|---|---|---|---|
| 未开相邻格 | 挖掘 | 左键 | 格碎裂 + 连锁展开(免费)+ 奖励身份揭显 | 意图入当帧批次 → 排雷玩法 `settle_batch` |
| 未开相邻格 | 挖掘 | 左键 | 格碎裂 + 连锁展开(免费)+ 奖励身份揭显 | 意图入当帧批次 → 排雷玩法的意图批次提交 |
| 已开格 | 移动 | 左键 | 悬停时路径预览高亮(布局决策 3);执行 ≤150ms 短滑 | 意图入批(BFS 移动,免费) |
| 未开格 | 封印 | 右键 | 封印印记落下(封印黑) | 意图入批(1 AP / 免费;已封印格右键无操作——封印印记无移除手段,封印不可逆批 2026-09-19) |
| 停手按钮 | 主动结束轮次 | 左键 | 按钮下压 + AP→RAP 飞行数字 | `settle_batch([stop])`(**经排雷玩法批次,不直调行动经济**——单意图帧批次契约) |
| 弃图刷新按钮(雷图右上角) | 当轮就地换图继续挖 | 左键 | 确认微交互(RAP−8 代价显式 + 旧板弃置提示);RAP<8 → 资源面拒绝可见(行动经济 EC13) | `request_refresh`(**Active 相位当场换图继续挖**——AP 不重置、本轮剩 N 次扣减;**上游已修订受理——见布局决策 4/OQ11**) |
| 停手按钮 | 主动结束轮次 | 左键 | 按钮下压 + AP→RAP 飞行数字 | 停手随当帧意图批次提交(**经排雷玩法批次,不直调行动经济**——单意图帧批次契约) |
| 弃图刷新按钮(雷图右上角) | 当轮就地换图继续挖 | 左键 | 确认微交互(RAP−8 代价显式 + 旧板弃置提示);RAP<8 → 资源面拒绝可见(行动经济 EC13) | 刷新意图提交(**Active 相位当场换图继续挖**——AP 不重置、本轮剩 N 次扣减;**上游已修订受理——见布局决策 4/OQ11**) |
| 图例按钮 | 开图例 | 左键 | 模态 overlay | 恒可开(图例条目恒在) |
| 结算明细带 | ~~展开回看~~(已随 P-C7 退役——带保留单行摘要,回看归「生涯」) | —— | —— | —— |
| 状态小图标(B 区站桩) | 悬停 | hover | 状态 tooltip(来源+语义+剩余结算次数) | 只读 |
| 胜利 overlay | 下一节点 | 左键 | → 关卡画面(选路枢纽)/ 关卡结算(末节点) | LevelRunner 推进 |
| 胜利 overlay | 下一节点 | 左键 | → 关卡画面(选路枢纽)/ 关卡结算(末节点) | 关卡推进方推进 |
| 失败 overlay | 返回 | 左键 | → 存档/营地 | 冒险落幕 |
**单意图帧纪律**(排雷玩法×6⑥):一帧内 collected 意图整批提交,stop 点击终结当帧收集;F4 语义序仲裁为防御性兜底。悬停预承诺三态光标:移动(箭头)/挖(镐)/封印(印记形)——左键双义(移动 vs 挖)的消歧完全依赖格态一瞥可辨 + 光标区分(误挖不可逆的感知保险)。
@@ -279,18 +275,18 @@
## Events Fired
项目无分析系统;本节 = **呈现层订阅的核心信号**(组合根直连 signal)+ playtest 遥测义务(playtest_logger.gd 已有五类行)。呈现层新遥测(写 playtest JSONL `ux` 行):
项目无分析系统;本节 = **呈现层订阅的核心信号**(组合根直连信号)+ 逐局遥测义务(逐局遥测日志已有五类行)。呈现层新遥测(写入逐局遥测的呈现层行):
| Player Action | Event Fired | Payload / Data |
| 事件(中文名) | 采集内容 | 载荷 / 数据 |
|---|---|---|
| 每轮结束 | `ux_round_input_ratio` | 原始输入动作数∶AP 消耗(行动经济 P4 ≤2 护栏观测,排雷玩法遥测义务) |
| 停手/清图 | `ux_round_exit` | path_tag {stopped, ap_exhausted, cleared}(对齐上游封闭枚举)——弃图率(难度曲线与Boss OQ4)观测 |
| 弃图刷新(挖掘期,非轮出口) | `ux_refresh_attempt` | {result: accepted/rejected_rap/rejected_cap, round_refresh_index: 1..2}——刷新使用(难度曲线与Boss OQ6)与行动经济 OQ3 预警阈值观测;每轮 0–2 次(布局决策 4 cap=2) |
| 拒绝/voided | `ux_reject` | {source: board/resource/phase, sub_reason} |
| 提示触发/关闭 | `ux_hint_shown` | {hint_id: chord/no_adjacent} |
| 呈现订阅(不改核心) | 排雷玩法三事件 / [战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行协议,含终局判定行) / battle_over / level_clear | 载荷 schema 成文义务见 §Data Requirements——旧版状态记录 entry 17①(HUD框架硬性要求) |
| 每轮结束 · 轮次输入比遥测 | 原始输入动作数∶AP 消耗(行动经济 P4 ≤2 护栏观测,排雷玩法遥测义务) | —— |
| 停手/清图 · 轮次出口遥测 | 弃图率(难度曲线与Boss OQ4)观测;出口路径对齐上游封闭枚举 | 出口路径标签 {停手 / AP 耗尽 / 清图} |
| 弃图刷新(挖掘期,非轮出口) · 刷新尝试遥测 | 刷新使用(难度曲线与Boss OQ6)与行动经济 OQ3 预警阈值观测;每轮 0–2 次(布局决策 4 cap=2) | {受理结果: 受理 / 拒因 RAP 不足 / 拒因触上限, 本轮第几次刷新: 1..2} |
| 拒绝/voided · 拒绝遥测 | 拒绝来源(板面/资源/相位三源)与细分原因 | {拒绝来源: 板面/资源/相位, 细分原因} |
| 提示触发/关闭 · 提示展示遥测 | 提示类型 | {提示类型: chord 提示 / 无相邻可挖格} |
| 呈现订阅(不改核心) | 排雷玩法三事件 / [战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行协议,含终局判定行) / 战斗结束 / 关卡完成 | 载荷 schema 成文义务见 §Data Requirements——旧版状态记录 entry 17①(HUD框架硬性要求) |
**持久状态写入**全部经组合根唯一路径(settle_batch / request_refresh / runner 推进)——UI 层禁直调核心写(架构七原则"依赖单向组合根")。
**持久状态写入**全部经组合根唯一路径(意图批次提交 / 刷新意图提交 / 关卡推进)——UI 层禁直调核心写(架构七原则"依赖单向组合根")。
---
@@ -302,13 +298,13 @@
| 挖掘→结算 | 明细带自底展开(150ms)+ 棋盘去饱和 + 金橙色相偏移 + 阶段指示翻转 | §2.2 色彩转金橙主导、饱和拉高、明度不变(状态区分经饱和/色相——art-bible §2 合规化 2026-09-12) |
| 结算逐行兑现 | 光带自棋盘行起点横扫 → 开格化箭雨/印记化暴击弹道飞向敌人立绘 → 受击闪白 + 血条直角递减 | §2.2 承载元素;因果全程可见可归因(P2) |
| 清图换新图 | 金粒喷泉 3–5s → 旧图碎裂淡出 → 新图 install 淡入 | §2.3 短促丰收不拖节奏 |
| RAP 清零(battle_over) | RAP 数字滚动归零(D 区 AP 条)+ 「随战斗结束消散」微文案 | 行动经济可见反馈义务 |
| RAP 清零(战斗结束) | RAP 数字滚动归零(D 区 AP 条)+ 「随战斗结束消散」微文案 | 行动经济可见反馈义务 |
| 敌人死亡 | 血条直角递减至零 → 卡体灰化 + 碎裂退场(禁瞬消) | 敌人系统资产下限(同等 blocking);动画密度归战斗表现 |
| 状态图标行全清(battle_over) | 状态逐枚化光收回(B 区站桩图标行)+ 「状态随战斗结束消散」微文案 | 增益状态×4④;RAP 清零同款先例 |
| 状态图标行全清(战斗结束) | 状态逐枚化光收回(B 区站桩图标行)+ 「状态随战斗结束消散」微文案 | 增益状态×4④;RAP 清零同款先例 |
| ~~关卡收束清库~~ | 义务随秘宝过渡形态退役消解——现行形态 = 营地同调装配、整局冒险生效,无关卡域清库面(秘宝.md) | —— |
| 胜利 | 蜡封自上落下弹跳定格 → 碎砖拼纹章 → overlay 暖金渐显 | §2.4 克制不狂欢 |
| 失败 | 全屏明度不变转暮蓝色相 → 印记化光点 800ms 逐枚收回 → overlay | §2.5 最静一拍 |
| 减动效选项 | 上述全部动画提供时长减半/直接跳终态开关(基线权威 = HUD框架「可访问性基线」节;开关承载 = 设置面(迭代 8 卡④已转正,settings_overlay)——2026-09-27 拍板不另立无障碍专项文档) | 动议:结算逐行兑现为信息载体不可整体跳过,可加速 |
| 减动效选项 | 上述全部动画提供时长减半/直接跳终态开关(基线权威 = HUD框架「可访问性基线」节;开关承载 = 设置面(迭代 8 卡④已转正)——2026-09-27 拍板不另立无障碍专项文档) | 动议:结算逐行兑现为信息载体不可整体跳过,可加速 |
---
@@ -316,24 +312,24 @@
| Data | Source System | Read / Write | Notes |
|---|---|---|---|
| 棋盘格态/数字/可挖域 | 排雷玩法 只读面(query) | Read | 呈现刷新面 = 排雷玩法 三事件信号(载荷 schema 成文——旧版状态记录 entry 17①) |
| BFS 移动路径 | 排雷玩法 `compute_move_path` | Read | 布局决策 3 合法性指示消费 |
| 拒绝原因 | 排雷玩法 `sub_reason` 封闭枚举 + 行动经济 资源面 | Read | 三源(board/resource/phase)映射文案——呈现层契约成文;资源面原因无上游载荷(行动经济 仅 bool + debug 诊断),客户端按动作上下文推断 |
| AP / RAP / 本轮刷新计数 | 行动经济 | Read | 常显;刷新入口可用性 = RAP 读数客户端预判(RAP≥8)+ `request_refresh` 返回值(行动经济 无 `can_request_refresh` API——仅 bool 返回 + 只读观测量;行动经济 EC13 拒绝合法可见) |
| 棋盘格态/数字/可挖域 | 排雷玩法 只读查询面 | Read | 呈现刷新面 = 排雷玩法 三事件信号(载荷 schema 成文——旧版状态记录 entry 17①) |
| BFS 移动路径 | 排雷玩法 路径规划只读面 | Read | 布局决策 3 合法性指示消费 |
| 拒绝原因 | 排雷玩法 细分原因封闭枚举 + 行动经济 资源面 | Read | 三源(板面/资源/相位)映射文案——呈现层契约成文;资源面原因无上游载荷(行动经济仅回受理与否,细分原因仅调试诊断可见),客户端按动作上下文推断 |
| AP / RAP / 本轮刷新计数 | 行动经济 | Read | 常显;刷新入口可用性 = RAP 读数客户端预判(RAP≥8)+ 刷新意图提交的受理回执(行动经济 无刷新可用性预查询——仅受理与否的回执 + 只读观测量;行动经济 EC13 拒绝合法可见) |
| 结算流水行(9 类封闭协议) | [战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 流水协议 | Read | 逐行来源标注(P2);未生效类行(奖励未生效)聚合计数呈现 |
| 判定 verdict | 战斗结算 判定段 | Read | 终局画面数据锚 |
| 敌单位 hp/顺位/摇定动作(pending_skill/type/name)/敌方增益/enemy_id/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**despawn 前捕获**(旧版状态记录 entry 17③——缺陷②教训) |
| 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算 SettlementTable 效果语义,非 magnitude 符号(增益状态×③) |
| 已开格奖励身份(奖励徽章) | 地图战斗资源 `query_identity`(经 board_view 身份解析只读回调,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory BoardView 行 |
| 节点图/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | 关卡画面(独立屏)节点态呈现(战斗屏零进度承载) |
| 意图提交 | 排雷玩法 | **Write** | `settle_batch`(stop 亦经此——不直调行动经济) |
| 弃图刷新 | 行动经济 | **Write** | `request_refresh`(挖掘期 Active 当场受理——布局决策 4 2026-09-12 上游已落稿;战斗结算 `refresh_board` 编排承载) |
| 节点推进 | LevelRunner | **Write** | 胜利 overlay「下一节点」 |
| 终局画面数据锚 | 战斗结算 判定行(流水行协议) | Read | 「终局判定行 = 胜负画面的数据锚」(战斗结算 UI Requirements);despawn 前状态可见性纪律 = 旧版状态记录 entry 17③(销毁前捕获、**载荷数据自包含**、禁呈现层回读核心生命周期状态);若需独立终局快照信号,须回战斗结算 GDD 修订补登记(战斗结算 现无 `settlement_snapshot` 契约——2026-09-12 评审修正) |
| 判定胜负 | 战斗结算 判定段 | Read | 终局画面数据锚 |
| 敌单位 生命/顺位/摇定动作(技能标识·类型·名称)/敌方增益/敌单位标识/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**销毁前捕获**(旧版状态记录 entry 17③——缺陷②教训) |
| 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算效果表语义,非幅度符号(增益状态×③) |
| 已开格奖励身份(奖励徽章) | 地图战斗资源 身份解析只读回调(经棋盘视图,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory 棋盘视图行 |
| 节点图/当前节点/HP 携带 | 关卡节点地图轻量读面 · 关卡推进方 | Read | 关卡画面(独立屏)节点态呈现(战斗屏零进度承载) |
| 意图提交 | 排雷玩法 | **Write** | 意图批次提交(stop 亦经此——不直调行动经济) |
| 弃图刷新 | 行动经济 | **Write** | 刷新意图提交(挖掘期 Active 当场受理——布局决策 4 2026-09-12 上游已落稿;战斗结算的挖掘期刷新编排承载) |
| 节点推进 | 关卡推进方 | **Write** | 胜利 overlay「下一节点」 |
| 终局画面数据锚 | 战斗结算 判定行(流水行协议) | Read | 「终局判定行 = 胜负画面的数据锚」(战斗结算 UI Requirements);销毁前状态可见性纪律 = 旧版状态记录 entry 17③(销毁前捕获、**载荷数据自包含**、禁呈现层回读核心生命周期状态);若需独立终局快照信号,须回战斗结算 GDD 修订补登记(战斗结算 现无结算快照契约——2026-09-12 评审修正) |
**架构注记**:UI 不拥有任何游戏状态——全部读经核心只读面,全部写经组合根。呈现层信号载荷 schema 须在《HUD框架》成文(旧版状态记录 entry 17①,缺项即 blocking);场景级 headless 测试进 AC(entry 17②);覆盖层层级断言(缺陷⑤——任何模态覆盖层必须有"打开时遮挡物隐藏"断言)。
**更新触发注记(2026-09-12 评审增补——防实现层等待不存在的信号,旧版状态记录 entry 17 教训同类)**:封印与移动成功反馈**零事件外发**(排雷玩法发射面封闭)——经意图调用 `ActionOutcome` 返回值命令式驱动;AP/RAP 读数:行动经济无推送信号——组合根每次调用返回后重查;状态效果:增益状态为纯被动状态池、零信号——状态栏刷新绑战斗结算的编排完成时点;奖励徽章:地图战斗资源 身份解析经 `query_identity` 只读回调(组合根注入 board_view),已开格揭显时查询——写路径回执后重查。
**更新触发注记(2026-09-12 评审增补——防实现层等待不存在的信号,旧版状态记录 entry 17 教训同类)**:封印与移动成功反馈**零事件外发**(排雷玩法发射面封闭)——经意图调用的返回值命令式驱动;AP/RAP 读数:行动经济无推送信号——组合根每次调用返回后重查;状态效果:增益状态为纯被动状态池、零信号——状态栏刷新绑战斗结算的编排完成时点;奖励徽章:地图战斗资源 身份解析经只读回调(组合根注入棋盘视图),已开格揭显时查询——写路径回执后重查。
---
@@ -355,7 +351,7 @@
- **源语言中文(CJK)**:运行时默认字体无 CJK 字形——**CJK 字体随包为硬性前置**(已锁),实现 AC 覆盖
- **按钮文案单行约束**:「停手」「弃图刷新」——按钮宽度按 4 汉字上限设计;译为英文("Hold"/"Reroll")不超 1.6× 不破版;德语长词("Aufgeben+Neu"类复合词)风险 HIGH——弃图刷新按钮允许两行或图标+缩写
- **明细带行文案**:来源标注格式「{行为} {值} → {目标}(来源)」为模板插值,最长 token = 来源标签(4 汉字,如「清图奖励」「封印未中」——settlement source_label_map 封闭枚举);行模板预留 40% 膨胀宽度(固定槽高 + clip 兜底)
- **明细带行文案**:来源标注格式「{行为} {值} → {目标}(来源)」为模板插值,最长 token = 来源标签(4 汉字,如「清图奖励」「封印未中」——战斗结算的来源标签封闭枚举);行模板预留 40% 膨胀宽度(固定槽高 + 裁剪兜底)
- **数字格式**:全部整数,无本地化格式需求;AP 点阵为图形载体不依赖文本
- **术语表锚定**:封印/雷/停手/弃图刷新/轮次/结算 已在 GDD 体系内定名,译文须同步 `design/gdd/术语表.md`
@@ -374,11 +370,11 @@
**交互与反馈**
- [ ] 挖/移/封印/停手/刷新全部操作纯鼠标可完成;stop 意图经 `settle_batch` 提交(代码走查 + 探针断言)
- [ ] 挖/移/封印/停手/刷新全部操作纯鼠标可完成;stop 意图经意图批次提交(代码走查 + 探针断言)
- [ ] 每次拒绝(棋盘面/资源面/相位)玩家可感知且两源可区分;voided(至少 stop 作废)可感知(排雷玩法×6⑥、行动经济双源)
- [ ] 弃图刷新消耗 RAP 8 点、本轮上限 2 次可见(按钮文案实时「剩 N 次」;上限已随行动经济 2026-09-12 修订生效);RAP<8 资源面拒绝可见(行动经济 EC13);确认交互与常规确认不同位;换图后 AP 不重置、剩余次数扣减可见(**上游已修订受理——2026-09-12 布局决策 4 落稿**)
- [ ] battle_over 时 RAP 清零有可见反馈(行动经济);~~关卡收束清库反馈~~(义务随秘宝过渡形态退役消解——现行 = 营地同调装配整局生效,秘宝.md)
- [ ] battle_over 时状态图标行全清有可见反馈(B 区站桩逐枚化光 + 微文案)(增益状态×④)
- [ ] 战斗结束时 RAP 清零有可见反馈(行动经济);~~关卡收束清库反馈~~(义务随秘宝过渡形态退役消解——现行 = 营地同调装配整局生效,秘宝.md)
- [ ] 战斗结束时状态图标行全清有可见反馈(B 区站桩逐枚化光 + 微文案)(增益状态×④)
- [ ] 敌人死亡瞬间可辨识(卡体灰化 + 碎裂退场,禁瞬消);战斗段短路被掐灭的重击由行序可推断(敌人系统资产下限)
**信息与归因**
@@ -388,17 +384,17 @@
- [ ] 状态效果:极性呈现映射成文且非 magnitude 符号;空态渲染成文(裁决:图标行隐藏——B 区站桩血条下零图标时不占位);满层溢出经明细行可见(增益状态×①③④)
- [ ] 图例面板条目恒在可随时回看、非模态提示不劫持输入
- [ ] 零合法板(棋盘生成 EC5 降级)出现时玩家侧诚实提示(toast,非静默)
- [ ] 效果行 value 为单次贡献值,禁止与 pool_value(池合成值)相加呈现;零值伤害行可见不被吞;衰减行按聚合计数呈现(战斗结算 呈现细则)
- [ ] 效果行数值为单次贡献值,禁止与池合成值相加呈现;零值伤害行可见不被吞;衰减行按聚合计数呈现(战斗结算 呈现细则)
- [ ] 未生效类行(奖励未生效)携带形状冗余(icon/划线),非色相独载
- [ ] 奖励徽章绘制优先级:封印印记之下、变异纹带之上、不进数字区;拾取瞬间 tier 档位可读(地图战斗资源 H1)
**场景级测试**(旧版状态记录 entry 17②③,缺项即 blocking)
- [ ] 场景级 headless 测试:真实实例化战斗屏场景 + 驱动完整对局 + 断言零脚本错误(ui_click_probe 模式扩展)
- [ ] 场景级 headless 测试:真实实例化战斗屏场景 + 驱动完整对局 + 断言零脚本错误(呈现层点击探针模式扩展)
- [ ] 任何模态覆盖层(图例/胜利/失败——明细回看已退役)有「打开时遮挡物隐藏/输入锁定」场景级断言(缺陷⑤对照)
- [ ] 动态区全部固定槽高 + clip_contents——悬停文案换行不推挤布局(缺陷①回归)
- [ ] 动态区全部固定槽高 + 裁剪兜底——悬停文案换行不推挤布局(缺陷①回归)
- [ ] CJK 字体随包,全部中文文案渲染非豆腐块
- [ ] 敌人 despawn 后呈现层不回读核心生命周期状态(快照纪律,缺陷②回归)
- [ ] 敌人销毁后呈现层不回读核心生命周期状态(快照纪律,缺陷②回归)
---
@@ -407,14 +403,14 @@
1. **Player journey map——已完成(2026-09-27 欠账处置拍板:不建)**:实现前补建的前提随战斗屏实装失效;情绪曲线事实源 = art-bible §2 能量档,真人体验观测与结论回流 = [体验验证清单](../../../体验验证清单.md)
2. **无障碍专项文档——已完成(2026-09-27 拍板:不另立 design/accessibility-requirements.md)**:基线(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体)权威 = [HUD框架](../systems/表现与支撑/HUD框架.md)「可访问性基线」节;正式分层(文本缩放/色盲模式/按键重映射)归 [设置与本地化](../systems/表现与支撑/设置与本地化.md) 待设计问题清单 #1(设置项全集裁决时定);本 spec §Accessibility 为呈现侧落地承诺
3. **interaction-patterns.md——已建成**(2026-09-12 /team-ui 批当日落稿,本条「未建」系过期陈述):本 spec 引入的四 pattern(双源拒绝反馈/逐行兑现明细带/悬停预承诺光标/点阵资源显示)均已入库成文(pattern 1/3/4/5)
4. **战斗表现美术测试**(game-concept 已登记,战斗表现批次前强制):本 spec 的常驻双舞台分屏(布局决策 1)是测试的布局输入——**敌人卡形态已裁决(2026-09-20 用户看对照样张拍板):全站桩式(无框立绘+底座),主角同样站桩、状态小图标挂血条下,卡片式整体退役**;本 spec 已随裁决改写(原"按卡片式起草"条款作废),站桩式不改变分区结构。对照样张存档 `production/mockups/enemy-card-vs-standee-mockup.html`
4. **战斗表现美术测试**(game-concept 已登记,战斗表现批次前强制):本 spec 的常驻双舞台分屏(布局决策 1)是测试的布局输入——**敌人卡形态已裁决(2026-09-20 用户看对照样张拍板):全站桩式(无框立绘+底座),主角同样站桩、状态小图标挂血条下,卡片式整体退役**;本 spec 已随裁决改写(原"按卡片式起草"条款作废),站桩式不改变分区结构。对照样张已存档(敌人卡与站桩式对照样张)
5. **移动动画形态**(布局决策 3):≤150ms 短滑为推荐值,playtest 观测「移动太瞬/太拖」反馈后调
6. **明细带挖掘期是否常驻上轮摘要**:常驻(现方案)vs 结算后即收起——playtest 观测回看频率
7. **Gamepad 导航序**:Partial 支持(非 MVP 门禁)——焦点环顺序待 gamepad 批次定
8. **NodeOverlay / 关卡结算屏——已完成(2026-09-27 欠账处置拍板;节点奖励屏部分被 2026-09-29 迭代 7 取代)**:关卡结算屏 spec 已建 [level-clear.md](level-clear.md)(本 spec 只锁出口锚点不变);节点奖励屏 spec 已出生 [node-reward.md](node-reward.md) v1.0(原「不立独立 spec、随秘宝内容重定批交付」拍板随之取代;NodeOverlay 现行:恢复点全量服务、奖励点=三选一构筑点)
9. **TunePanel 退役计划——已完成(迭代 15 卡①,2026-10-05)**:TunePanel 类整体吸收退役入调试浮窗 DebugOverlay(DS_DEBUG=1 门控);正式侧归元进度(天赋树)不变(门控表述属当期留痕——调试入口现发布态恒隐,现行口径见元素表「调试浮窗」行与「调试变体」行)
8. **NodeOverlay / 关卡结算屏——已完成(2026-09-27 欠账处置拍板;节点奖励屏部分被 2026-09-29 迭代 7 取代)**:关卡结算屏 spec 已建 [level-clear.md](level-clear.md)(本 spec 只锁出口锚点不变);节点奖励屏 spec 已出生 [node-reward.md](node-reward.md) v1.0(原「不立独立 spec、随秘宝内容重定批交付」拍板随之取代;节点覆盖层现行:恢复点全量服务、奖励点=三选一构筑点)
9. **调参面板退役计划——已完成(迭代 15 卡①,2026-10-05)**:调参面板类整体吸收退役入调试浮窗(调试开关=1 门控);正式侧归元进度(天赋树)不变(门控表述属当期留痕——调试入口现发布态恒隐,现行口径见元素表「调试浮窗」行与「调试变体」行)
10. **提示文案定稿**:chord 提示/空态文案归实现期文案 pass,本 spec 只锁必含要点与形态约束
11. **弃图刷新上游修订(已落稿 2026-09-12,布局决策 4)**:行动经济(Active 相位受理 + 每轮上限 2 次 + Waiting 代理退役 + 旧旗作废裁决 + 时间税失效预警)、排雷玩法(Active·仅刷新路径装板受理)、战斗结算(stopped/ap_exhausted 恒 same_board + `refresh_board` 挖掘期刷新编排;AC-29 改写/AC-30 恒 same_board)、registry `rap_rules` 同步、旧版 systems-index 修订块登记。遗留观察项:当场刷新的节奏健康由行动经济 OQ3 弃图率/刷新率预警承载,playtest 复核
11. **弃图刷新上游修订(已落稿 2026-09-12,布局决策 4)**:行动经济(Active 相位受理 + 每轮上限 2 次 + Waiting 代理退役 + 旧旗作废裁决 + 时间税失效预警)、排雷玩法(Active·仅刷新路径装板受理)、战斗结算(停手与 AP 耗尽两出口恒同盘不变 + 挖掘期刷新编排;AC-29 改写/AC-30 恒同盘不变)、系统索引的刷新规则同步、旧版 systems-index 修订块登记。遗留观察项:当场刷新的节奏健康由行动经济 OQ3 弃图率/刷新率预警承载,playtest 复核
12. **教学引导缺口挂靠(成文时教学引导 GDD 尚未建,留痕)**:挖到雷 = 敌方优势而非死亡(排雷玩法登记「经典扫雷玩家最大预期违背」)、旗挡链(连锁止于旗属正常)、首次蓄力姿态一次性提示(敌人系统)——本屏 toast 体系为落点候选,随教学引导批次定
13. **敌方 AC 展示——已终裁完成(2026-09-26)**:用户终裁——敌方 AC 数值**全屏不展示**(任何区均不直接写上);护甲类型改**图标方式**展示(形状+色相双通道,终稿图标资产归美术批);敌人站桩底座下名字行退回**纯名字 + 护甲类型图标**。与旧「克制对策可读性义务」(敌人技能池批)的关系 = **义务降级承载**:类型保留(图标可读,对策判断仍可做)、数值退役(不再直接展示);敌人技能池批相关义务措辞由并行代码批同步。我方 AC 仍按 2026-09-26 二轮拍板保留于 C 区攻击数据框(唯一展示面)
14. **GP/钥石迁出战斗屏后的展示面归属——已拍板完成(2026-09-26 二轮)**:GP = 关卡画面常驻总量 + 战斗总结结算(总量+增量);钥石 = 冒险总结结算 + 营地。呈现 spec 已出生:[settlement-summary.md](settlement-summary.md) v1.0(2026-09-30——两面分节轻量 spec,本 OQ 为其命名与分工依据;「战斗结算画面」「结算画面」旧措辞随 spec 命名完成退役)
@@ -437,7 +433,7 @@
| 战斗结算×3 | ①高光行强调呈现(封印标中金框行/清图 H2 金框行)②完美封印呈现形态 ③棋盘生成 EC5 零合法板提示/补偿/回退形态(toast 提示;补偿/回退不提供——三形态选型留痕 2026-09-12) | §States 高光/清图 + §States 拒绝子态 + §AC;注:结算明细屏 = 战斗结算 UX Flag 事项(本 spec 即其 /ux-design 产物),非 战斗结算×3 状态记录 |
| 敌人系统×4 | 意图映射/恒定/顺位/敌方池 | §Component Inventory 敌人站桩立绘 + **意图线索映射目录(敌人系统×4① 成文占位,资产定稿归战斗表现)** + AC |
| 增益状态×4 | 常显+空态/来源标注/溢出/去留 | §States 空态 + §AC |
| 地图战斗资源×5 | 身份恒读/明细行/清图强调(旧版状态记录五条中「窗口」「清空反馈」随格面资源批下线——清空反馈义务后载秘宝背包、复随秘宝过渡形态退役消解(现行 = 营地同调装配,秘宝.md)) | §Component Inventory BoardView 奖励徽章 + §States 清图/高光 + §Data Requirements query_identity + AC |
| 地图战斗资源×5 | 身份恒读/明细行/清图强调(旧版状态记录五条中「窗口」「清空反馈」随格面资源批下线——清空反馈义务后载秘宝背包、复随秘宝过渡形态退役消解(现行 = 营地同调装配,秘宝.md)) | §Component Inventory 棋盘视图奖励徽章 + §States 清图/高光 + §Data Requirements 身份解析只读回调 + AC |
| 棋盘生成 | EC5 零合法板诚实呈现(数据面经 战斗结算×3③ 上抛) | §States 拒绝子态(toast)+ AC |
| 敌人系统资产下限 | 受击反馈/死亡表现/顺位标记 | §Transitions 受击 + §States 敌人死亡呈现(禁瞬消)+ §Component 顺位徽标;呈现语义在本 spec(同等 blocking),动画密度归战斗表现 |
+9 -7
View File
@@ -1,6 +1,6 @@
# 营地场景 UX spec(camp)
> 状态:v2.1(2026-10-24 营地场景图美术设计段;取代旧「四柜台覆盖层」形态)
> 状态:v2.2(现行)
> 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制)
> 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.md)
> 相邻:[level-map.md](level-map.md)(主菜单语境)
@@ -16,7 +16,7 @@
| 大门(半开的门扇,门洞内一条通向远处的小路) | 冒险 | 点大门永远先出确认:有未完冒险=三出口(继续上一段/开始新冒险/取消);无=是否开始新冒险 |
| 锻炉·铁砧 | 锻造工坊 | **未解锁=禁用态**(降明度+去饱和) |
| 宝箱 | 秘宝匣 | **未解锁=禁用态**(同上) |
| 帐篷旁笔记本 | 生涯记录 | 生涯页返回钮文案**禁含「营地」字样**(`ui_struct_probe.gd` nojump 禁词表) |
| 帐篷旁笔记本 | 生涯记录 | 生涯页返回钮文案**禁含「营地」字样**(由呈现层结构校验兜底,口径见测试规约) |
| 木桩 | 天赋树 | |
| 布告板 | 奖励格图鉴 | 内容冻结未激活,点进看占位——**不是禁用态** |
| 篝火 | 离开营地回主菜单 | **必须二次确认**;命中区只含火堆、不含火焰 |
@@ -24,13 +24,15 @@
纵深=远景天空与远山/中景木围墙与营地大门/近景篝火与左右物件(作者纵深稿)。中景围墙是横贯全宽的密排竖木桩墙——木桩一根挨一根无缝、桩顶削尖、无横木,只在中段留缺口给大门。
**中景带自画面底部往上约 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 美术方案讨论留痕见 [提案](../../../reports/提案-营地场景图美术生成方案-2026-10-22.md)。
## 点选反馈(根态)
悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附)+物件**名牌**(物件下方 8px 居中,FS_HINT 14,对比 ≥4.5:1);按下=位移 1px;禁用=降明度+去饱和;已解锁=常态。**四态零新素材**(全部程序化)。
悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附)+物件**名牌**(物件下方 8px 居中,提示字号 14,对比 ≥4.5:1);按下=位移 1px;禁用=降明度+去饱和;已解锁=常态。**四态零新素材**(全部程序化)。
命中区:七件两两不相交、不越出画面、**不侵入顶栏禁区(y ≤ 48)**。
@@ -40,10 +42,10 @@
## 叠层与开合
各界面开合走模态栈单口,层序入 `LAYER_CONTRACT`。**承载形态(独立全屏页 vs 单一内页切换)与叠层规则归卡④ §9 代拍板域,本 spec 不预设值**;派生规则:独立全屏页=场景根隐藏/单一内页切换=场景根留底(「缩放虚化」为第三形态,禁批内自选)。ESC 浮窗(返回主菜单/设置/退出游戏)归卡⑤。
各界面开合走模态栈单口,层序按模态栈契约登记。**承载形态(独立全屏页 vs 单一内页切换)与叠层规则归卡④ §9 代拍板域,本 spec 不预设值**;派生规则:独立全屏页=场景根隐藏/单一内页切换=场景根留底(「缩放虚化」为第三形态,禁批内自选)。ESC 浮窗(返回主菜单/设置/退出游戏)归卡⑤。
## 口径
- 呈现数据源=MetaProgression 只读投影经组合根喂入;本层不持有系统资源、不算价。
- 呈现数据源=元进度系统只读投影经组合根喂入;本层不持有系统资源、不算价。
- 「最近冒险」条带已撤下(卡④ Q4=甲),逐局历史回看归生涯。
- 秘宝匣界面内部呈现(解锁面+同调装配段)保留为附录,待该界面立独立 spec 后迁出。
+24 -24
View File
@@ -2,7 +2,7 @@
> **Status**: Complete(核心 8 成文〔2 号已退役留档〕+ 12 目录占位)
> **Author**: user + ux-designer
> **Last Updated**: 2026-10-15(简明技术中文用语规范批:词面修正 6 处)
> **Last Updated**: 2026-10-29
> **Template**: Interaction Pattern Library
> **来源**: `design/gdd/ux/battle.md`(2026-09-12 /ux-review APPROVED)已拍板内容的成文固化——本库不引入新设计决策
@@ -29,18 +29,18 @@
| 6 | 模态 overlay 层级纪律 | Modal | **成文 ✓** |
| 7 | 破坏性确认异位 | Feedback | **成文 ✓** |
| 8 | 禁用态即理由 | Feedback | **成文 ✓** |
| 9 | 常驻意图徽标 | Data Display | **已实现**(enemy_card_view.gd——规格全文待补) |
| 9 | 常驻意图徽标 | Data Display | **已实现**(意图徽标呈现件——规格全文待补) |
| 10 | 一次性非模态教学提示 | Feedback | **已实现**(chord 提示——规格全文待补) |
| 11 | tooltip 规范 | Feedback | **已实现**(状态/道具悬停——规格全文待补) |
| 12 | 单意图帧批次提交 | Input | **已实现**(main.gd 组合根——规格全文待补) |
| 13 | 状态效果槽 | Data Display | **已实现**(status_slot_row.gd——规格全文待补;载体变更留档:C 区四槽已退役,现役载体=B 区站桩伴随件) |
| 14 | 步进进度条 | Navigation | **已退役留档**(level_strip_view 已删,节点态承载进度) |
| 15 | 移动路径预览 | Input | **已实现**(board_view.gd BFS 蓝点——规格全文待补) |
| 16 | 清图/换图演出编排 | Feedback | **已实现·占位级**(board_fx_view.gd 金粒彩纸——规格随战斗表现 美术批补) |
| 17 | 胜利/失败终局编排 | Feedback | **已实现·占位级**(battle_end_overlay.gd——规格随战斗表现补) |
| 12 | 单意图帧批次提交 | Input | **已实现**(组合根——规格全文待补) |
| 13 | 状态效果槽 | Data Display | **已实现**(状态图标行呈现件——规格全文待补;载体变更留档:C 区四槽已退役,现役载体=B 区站桩伴随件) |
| 14 | 步进进度条 | Navigation | **已退役留档**(步进进度条呈现件已删,节点态承载进度) |
| 15 | 移动路径预览 | Input | **已实现**(棋盘视图 BFS 蓝点——规格全文待补) |
| 16 | 清图/换图演出编排 | Feedback | **已实现·占位级**(棋盘演出呈现件 金粒彩纸——规格随战斗表现 美术批补) |
| 17 | 胜利/失败终局编排 | Feedback | **已实现·占位级**(战斗终局覆盖层——规格随战斗表现补) |
| 18 | Boss 遭遇演出 | Feedback | **已实现·占位级**(紫赤影子——规格随战斗表现补) |
| 19 | 库存常读列表 | Data Display | **占位失效**(所记载体 inventory_panel_view.gd 从未入库〔git 全史零记录,2026-10-09 复核〕;库存面已随秘宝过渡形态退役——C 区现行秘宝槽=装配呈现面、重接归装配面批次,见 battle.md 组件清单) |
| 20 | 标准控件组(button/progress bar/list) | Control | **已实现**(ui_kit.gd 直角细线/凸圆角——规格全文待补) |
| 19 | 库存常读列表 | Data Display | **占位失效**(所记载体从未入库;库存面已随秘宝过渡形态退役——C 区现行秘宝槽=装配呈现面、重接归装配面批次,见 battle.md 组件清单) |
| 20 | 标准控件组(button/progress bar/list) | Control | **已实现**(标准控件组 直角细线/凸圆角——规格全文待补) |
MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab bar——战斗屏无此需求;TunePanel slider 为调试专用。
@@ -53,11 +53,11 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Category**: Feedback
**Used In**: 战斗屏(棋盘/资源全部意图动作)
**Description**: 玩家意图被核心拒绝时,反馈必须同时做到:(a) 每次拒绝玩家可感知(防静默拒绝读感欺骗);(b) 来源可区分——棋盘面(排雷玩法判,携 sub_reason 封闭枚举)/资源面(行动经济判,客户端推断)/相位(排雷玩法 ActionOutcome)三源各异;(c) 图标+文案双通道,非色相独载。
**Description**: 玩家意图被核心拒绝时,反馈必须同时做到:(a) 每次拒绝玩家可感知(防静默拒绝读感欺骗);(b) 来源可区分——棋盘面(排雷玩法判,携细分原因封闭枚举)/资源面(行动经济判,客户端推断)/相位(排雷玩法意图回执)三源各异;(c) 图标+文案双通道,非色相独载。
**Specification**:
- 触发:`ActionOutcome{accepted=false, reject_reason, sub_reason}` **返回值驱动**(拒绝无事件外发——排雷玩法发射面封闭,实现层禁止等待拒绝信号)
- 反馈映射:board_face → 目标格危险红 `#A8321F` 边框闪(拒绝语义红族——旗朱→封印紫→封印黑 #000000,2026-09-28 用户拍板现行) + toast 具体原因(sub_reason→文案映射表:not_adjacent/target_revealed/target_concealed/flag_present/flag_absent/no_path/out_of_catalog);resource_face → 对应资源数字脉冲(AP/RAP)+「AP 不足 · 剩 0 点」式文案;phase → toast(结算中/非挖掘期)
- 触发:意图回执(受理标志 + 拒绝原因 + 细分原因)**返回值驱动**(拒绝无事件外发——排雷玩法发射面封闭,实现层禁止等待拒绝信号)
- 反馈映射:棋盘面 → 目标格危险红 `#A8321F` 边框闪(拒绝语义红族——旗朱→封印紫→封印黑 #000000,2026-09-28 用户拍板现行) + toast 具体原因(细分原因→文案映射表:非相邻 / 目标已开格 / 目标未开格 / 该格已有封印 / 该格无封印 / 无可达路径 / 坐标越界);资源面 → 对应资源数字脉冲(AP/RAP)+「AP 不足 · 剩 0 点」式文案;相位 → toast(结算中/非挖掘期)
- 即时性:拒绝当帧呈现;toast 自动消退 2.5s
- **voided 区分**:作废 ≠ 拒绝(零效果零扣费零事件)——须独立文案如「本轮已清图,停手未执行 · 剩余 AP 已作废」,至少 stop 作废可感知
- Accessibility:危险红边框闪为形状+运动通道;文案常驻
@@ -81,14 +81,14 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Category**: Data Display
**Used In**: 战斗画面区底部(结算期第一注视点)
**Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开 84px 实时流;点击开完整回看 overlay。
**Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开为实时流(带高读 `log_band_h_settle`);点击开完整回看 overlay。
**Specification**:
- 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段 `type/source_class/origin_cell/round_index/display_name/value/highlight?`);**无来源行 = 断言失败**
- 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败**
- 行模板:「{行为} {值} → {目标}(来源)」——来源标注(第 N 轮 · 安全格式);行文案最长 token = 来源标签(4 汉字封闭枚举),模板预留 40% 膨胀宽度
- 聚合纪律:未生效类行(奖励未生效——现行 9 类行协议唯一未生效型,2026-09-20 收敛)按聚合计数呈现(防逐行惩罚轰炸);衰减行聚合计数;效果行 value=单次贡献值、禁与 pool_value(池合成值)相加呈现;零值行可见不吞(免伤轮归因不断点)
- 高亮:highlight 字段 {flag_hit, clear_reward_h2, perfect_broken} → 金框强调;完美封印 = 独立高光行 + 全屏喝彩级反馈。`perfect_broken` 的判据句读作「本板首次标错受检」,不等于完美资格已失——专长「完美保险」可在本场首次标错时豁免,实际资格按有效误标(误标总数 − 豁免数)判定
- 布局:固定槽高 + clip_contents;正文 ≥14px;数值暗底高对比;未生效类行带 icon/划线形状冗余
- 聚合纪律:未生效类行(奖励未生效——现行 9 类行协议唯一未生效型,2026-09-20 收敛)按聚合计数呈现(防逐行惩罚轰炸);衰减行聚合计数;效果行数值 = 单次贡献值、禁与池合成值相加呈现;零值行可见不吞(免伤轮归因不断点)
- 高亮:高亮标记(封印标中、清图奖励、完美破除) → 金框强调;完美封印 = 独立高光行 + 全屏喝彩级反馈。「完美破除」的判据句读作「本板首次标错受检」,不等于完美资格已失——专长「完美保险」可在本场首次标错时豁免,实际资格按有效误标(误标总数 − 豁免数)判定
- 布局:固定槽高 + 裁剪兜底;正文 ≥14px;数值暗底高对比;未生效类行带 icon/划线形状冗余
- 击杀标记呈现(H3 数据源 = 战斗结算流水击杀结果字段)
**When to Use**: 任何「因果可归因」的逐行流水呈现
@@ -101,7 +101,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
### 4. 悬停预承诺光标(hover pre-commitment cursor)
**Category**: Input
**Used In**: 扫雷图 BoardView
**Used In**: 扫雷图(棋盘视图)
**Description**: 左键双义(已开格=移动 / 未开相邻格=挖)的消歧完全依赖格态一瞥可辨 + 悬停光标区分——**误挖不可逆**(1 AP + 可能触雷)的感知保险。
@@ -130,7 +130,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Specification**:
- 形态:`●●●●●○○○` + 「5/8」;直角细线数据框(信息=直线直角,art-bible §3.3)
- 更新触发:组合根每次调用返回后重查(**行动经济无推送信号**——实现层禁止等待资源变更事件)
- 拒绝反馈:资源不足时数字脉冲(pattern 1 的 resource_face 腿)
- 拒绝反馈:资源不足时数字脉冲(pattern 1 的资源面腿)
- Accessibility:点阵=形状通道、数字=文本通道;对比 ≥4.5:1
**When to Use**: 小整数离散资源(≤16 量级)
@@ -150,7 +150,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Specification**:
- 打开:遮挡物隐藏或禁用 + 输入锁定;单一模态栈(无叠模态)
- 关闭:恢复被遮挡物;关闭按钮/再点入口切换
- 内容:固定槽高 + clip_contents(缺陷①——悬停文案换行禁止推挤布局)
- 内容:固定槽高 + 裁剪兜底(缺陷①——悬停文案换行禁止推挤布局)
- 验收:**场景级断言「打开时遮挡物隐藏/输入锁定」为每个模态 overlay 的强制 AC**(battle.md 场景级测试节)
**When to Use**: 全屏打断级信息、只读回看、仪式性终局
@@ -170,7 +170,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Specification**:
- 弃图刷新:确认微交互显式呈现全部代价(RAP−8 · **旧板封印印记随旧板作废**提示);确认交互位置与常规确认不同位
- 双通道:图标 + 文案冗余
- 拒绝路径:RAP<8 → pattern 1 resource_face(EC13 合法拒绝可见)
- 拒绝路径:RAP<8 → pattern 1 的资源面(EC13 合法拒绝可见)
**When to Use**: 任何消耗资源/不可逆的操作确认
**When NOT to Use**: 无代价操作(直接执行 + 直接反馈)
@@ -189,7 +189,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
**Specification**:
- 形态:灰置 + tooltip「HP 已满 · 禁选」;点击无效 + 原因 toast 兜底
- 语义区分:满血 = 零收益守卫(禁选、不烧包)≠ 缺血不足(超量 clamp 部分回复,**不**禁选)
- usable_now 由战斗结算组装进窗口载荷(地图战斗资源零跨系统读)——呈现层只消费不推断
- 「当前可用」标记由战斗结算组装进窗口载荷(地图战斗资源零跨系统读)——呈现层只消费不推断
**When to Use**: 任何可见但不可交互的元素
**When NOT to Use**: 完全隐藏的不可用功能(不存在的东西无需解释)
@@ -201,7 +201,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
## Gaps & Patterns Needed
- **目录 #9–#20 已全部实现**(2026-09-12 战斗屏实现批,冒烟 149/149 + 三探针全绿);规格全文回写 = HUD框架正式化批义务
- 实现期新增技术件(不单独立 pattern,规格归并):去饱和+金橙状态切换 = 逐格共享 ShaderMaterial(明度不变,gl_compatibility 安全);敌人死亡残位 = 卡体灰化+碎裂禁瞬消(归入 #17 终局编排规格)
- 实现期新增技术件(不单独立 pattern,规格归并):去饱和+金橙状态切换 = 逐格共享 ShaderMaterial(明度不变,兼容渲染器下安全);敌人死亡残位 = 卡体灰化+碎裂禁瞬消(归入 #17 终局编排规格)
- **MVP 未使用标准控件**:toggle / dropdown / input field / tab bar(无需求不设占位);TunePanel slider 为调试专用不入库
- **实现偏差规则**:pattern 近似但不完全适配时,实现层禁止静默变体——登记偏差交 ux-designer 复核后回写
+14 -14
View File
@@ -1,8 +1,8 @@
# UX Spec: 关卡结算屏(Level Clear)——提升三选一选择面
> **Status**: v2 义务锁定版(2026-10-01 迭代 10 奖励重构批重写——三件套快照展示退役,改三选一选择交互;呈现形态随实装批定,视觉终稿以表现层样张为前置,见 P1/OQ4;**本批渲染层验证冻结——目检清单顺带交付、实机目检待用户批准**)
> **Status**: v2 义务锁定版
> **Author**: user + ZCode(UX 文档欠账处置批;迭代 10 重写)
> **Last Updated**: 2026-10-15(简明技术中文用语规范批:词面修正 4 处;原标 2026-10-01 滞后于 2026-10-09 两条修订记录,本批一并对齐)
> **Last Updated**: 2026-10-29
> **Platform Target**: PC (Steam)——Keyboard/Mouse 主,全部操作纯鼠标可完成(同 battle.md)
> **Template**: UX Spec
> **来源**: battle.md OQ8 欠账处置(2026-09-27 用户拍板建档)+ 迭代 10 奖励重构批(2026-09-30 用户拍板案丙——三选一/部分回血/钥石挪转,计划书 m3-iter10-loop-plan-2026-09-30.md)
@@ -28,13 +28,13 @@
| 时刻 | 载体(目标形态,迭代 10 实装) | 呈现内容 |
|---|---|---|
| Boss 战胜利(非末关) | 战斗总结结算([settlement-summary.md](settlement-summary.md))→ 确认后进**本屏** | 战利品行含钥石增量行 → 本屏:部分回血反馈 + 三选一三卡 |
| 非末关完成(本屏默认态) | 三选一覆盖层(curio_pick_overlay——与奖励节点共用面) | 部分回血兑付行 + 三张专长选项卡(点选+确认+兑付反馈) |
| 非末关完成(本屏默认态) | 三选一覆盖层(与奖励节点共用面) | 部分回血兑付行 + 三张专长选项卡(点选+确认+兑付反馈) |
| 末关完成 / 失败 / 放弃 | 冒险结算(复用战斗终局 overlay) | 冒险摘要五件事 + 钥石入账明细——**相邻域**(冒险落幕面,非本屏职责) |
## Navigation Position
```
战斗屏 ──胜利(末节点 Boss)──▶ 战斗总结结算(GP/钥石增量行)──确认──▶ 【关卡结算(level_clear)】
战斗屏 ──胜利(末节点 Boss)──▶ 战斗总结结算(GP/钥石增量行)──确认──▶ 【关卡结算(关卡完成)】
├─ 非末关:回血反馈 + 三选一点选兑付 → 下一关开始(→ 关卡画面选路)
└─ 末关完成:直通冒险结算(相邻域)→ 返回存档/营地
失败不经本屏(战斗屏失败分支直达冒险落幕)。
@@ -47,7 +47,7 @@
| 方向 | 触发 | 携带语境 |
|---|---|---|
| 进入 | 战斗总结结算确认 → 关卡收束(先清库后宣布)→ 非末关兑付部分回血 → 三选一候选呈现(bonus_pick 信号) | 三候选(件 id + 名称 + 效果行值);部分回血前后生命读数;GP 账本余额(跨关延续) |
| 进入 | 战斗总结结算确认 → 关卡收束(先清库后宣布)→ 非末关兑付部分回血 → 三选一候选呈现(奖励三选一拉起信号) | 三候选(件 id + 名称 + 效果行值);部分回血前后生命读数;GP 账本余额(跨关延续) |
| 离开 | 三选一确认兑付 → 继续钮(唯一出口) | 兑付反馈载荷 → 下一关开始 |
零自动推进(冒险结构 契约 4):三选一呈现后必须玩家点选才兑付,确认前玩家不被推着走;重复兑付被拒。选择停点可中断:断点附随件扩「奖励选择态」,续玩回到本屏同一候选(不重抽)。
@@ -70,7 +70,7 @@
| 三选一选择态(默认) | 非末关完成,候选已抽定 | 三卡呈现(零自动推进)→ 点选 → 确认 → 兑付反馈 → 继续 |
| ~~同调位替换子态~~ | ~~点选秘宝件且库存满位~~ | 已随渠道退役(秘宝件出池、关卡完成候选 = 纯专长——本屏无道具件候选) |
| 兑付反馈态 | 确认兑付后 | 选中件效果一行 + 丢弃提示 → 继续钮点亮 |
| 末关完成 | level_clear 且末关 | 直通冒险结算——不发三选一与回血(奖励只朝下一战生效);本屏不出现 |
| 末关完成 | 关卡完成且末关 | 直通冒险结算——不发三选一与回血(奖励只朝下一战生效);本屏不出现 |
| Boss 入口语境 | 击杀 Boss(恒为本屏入口前置) | 入口演出归战斗屏;钥石增量行归战斗总结结算——本屏不另设 |
## Interaction Map
@@ -78,23 +78,23 @@
| 组件 | 动作 | 输入 | 即时反馈 | 结果(经组合根) |
|---|---|---|---|---|
| 提升选项卡 ×3 | 点选(单选高亮) | 左键 | 选中卡高亮、确认钮点亮 | 选中态暂存(未确认可改选) |
| 确认钮 | 兑付选中件 | 左键 | 兑付反馈(效果一行) | AdventureRunner 兑付恰一件(重复兑付被拒);另两件丢弃不可反悔 |
| 继续钮 | 接受结算、推进 | 左键 | 按钮下压 | 下一关开始(AdventureRunner.continue_to_next_level——兑付后受理) |
| 确认钮 | 兑付选中件 | 左键 | 兑付反馈(效果一行) | 冒险编排兑付恰一件(重复兑付被拒);另两件丢弃不可反悔 |
| 继续钮 | 接受结算、推进 | 左键 | 按钮下压 | 下一关开始(推进到下一关——兑付后受理) |
| ~~替换子面板钮~~ | —— | —— | —— | 已随渠道退役(本屏无道具件候选) |
复用既有交互范式,零新增原语:三卡点选 = 奖励节点 choice_box 按钮数组同族(node_overlay.show_reward_items 先例)+ 选路覆盖层点选确认两段式(level_map_screen 先例);覆盖层容器 = 中场确认覆盖层位置复用。
复用既有交互范式,零新增原语:三卡点选 = 奖励节点 选择框 按钮数组同族(由节点覆盖层展示奖励三选一 先例)+ 选路覆盖层点选确认两段式(关卡地图屏 先例);覆盖层容器 = 中场确认覆盖层位置复用。
## Data Requirements
| Data | Source System | Read / Write | Notes |
|---|---|---|---|
| 三候选(件 id/名称/效果行值) | 冒险结构 bonus_pick 信号(令牌池洗牌不放回抽 3〔同 kind 去重——v0.14 令牌池换代口径,原「加权洗牌」退役〕,reward_pick 封闭流) | Read | 载荷自包含、despawn 前捕获;同种子同三候选(可复放) |
| 三候选(件 id/名称/效果行值) | 冒险结构 奖励三选一拉起信号(令牌池洗牌不放回抽 3〔同 kind 去重——v0.14 令牌池换代口径,原「加权洗牌」退役〕,奖励抽取封闭流) | Read | 载荷自包含、销毁前捕获;同种子同三候选(可复放) |
| 部分回血读数(回血前后生命) | 冒险结构生命账本(clear_heal_pct 兑付) | Read | 满血回 0 合法态文案化 |
| 兑付反馈载荷 | 冒险结构 bonus_resolved 信号 | Read | 选中件 id + 生效值 |
| 兑付反馈载荷 | 冒险结构 奖励兑付完成信号 | Read | 选中件 id + 生效值 |
| ~~同调位库存~~ | —— | —— | 替换子态随渠道退役;过渡键 transitional_sync_slots 已退役(迭代 11① T7——同调位现行事实源 = attunement_growth.tres 谓词表实时求值) |
| 冒险摘要(五件事) | 冒险结构 adventure_finished 载荷 | Read | 末关分支数据锚(相邻域冒险结算屏消费) |
| 冒险摘要(五件事) | 冒险结构的冒险收束载荷 | Read | 末关分支数据锚(相邻域冒险结算屏消费) |
| GP 账本 | 冒险结构 GP 冒险账本 | — | 不进本屏(维持 2026-09-27 拍板) |
| 钥石入账 | 元进度 settle_adventure | Write(相邻域) | 唯一写档点不变;**本屏零钥石元素**(迭代 10 起,增量行迁战斗总结结算) |
| 钥石入账 | 元进度 冒险结算入账 | Write(相邻域) | 唯一写档点不变;**本屏零钥石元素**(迭代 10 起,增量行迁战斗总结结算) |
## Accessibility
@@ -103,7 +103,7 @@
## Acceptance Criteria
- [ ] 非末关完成必经本屏:三选一恰呈现一次(零自动推进)、点选后恰兑付一件(重复兑付被拒)、未选项零生效
- [ ] 三候选读数可见且与 bonus_pick 载荷一致(件名称 + 效果行值);同种子同三候选
- [ ] 三候选读数可见且与奖励三选一拉起载荷一致(件名称 + 效果行值);同种子同三候选
- [ ] 部分回血兑付行可见且封顶上限;满血回 0 呈合法态文案
- [ ] ~~同调位替换子交互~~(已随渠道退役——本屏无道具件候选,见 Information Hierarchy 第 4 条注)
- [ ] 末关完成分支正确直通冒险结算(不发三选一与回血),冒险摘要五件事完整
+27 -27
View File
@@ -1,8 +1,8 @@
# UX Spec: 关卡画面(选路枢纽,Level Map)
> **Status**: 定稿版——三项设计提案已拍板完成(2026-09-27:底栏状态展示 / 图上点击选路·候选按钮退役 / 放弃二次确认);**形态已升格独立屏(2026-09-28 用户裁决:独立存在为最终形态)**;**视觉终稿已冻结(2026-09-28 关卡画面样张 v1 机检 11/11 全绿,闸形 A——冻结值见 Layout/组件清单与样张机检报告,迭代 4)**
> **Status**: 定稿版(现行——三项设计提案已拍板:底栏状态展示 / 图上点击选路·候选按钮退役 / 放弃二次确认;形态为独立屏;视觉终稿已冻结)
> **Author**: user + ZCode(关卡画面 UX 批)
> **Last Updated**: 2026-10-25(迭代 35 生成图换代:呈现面随动登记)
> **Last Updated**: 2026-10-29
> **Platform Target**: PC (Steam)——Keyboard/Mouse 主,全部操作纯鼠标可完成(同 [battle.md](battle.md))
> **Template**: UX Spec
> **覆盖范围**: 独立屏形态(关卡停点全景)· 入口停点 · 岔路停点 · 单链停点 · 放弃冒险入口
@@ -61,24 +61,24 @@
### Layout Zones
按 1280×800 基座(2026-09-12 定)。本屏为独立屏独占呈现:屏底(深底 `BG_ROOT` + 石纹底噪〔迭代 2 框材组冻结件,缺位纯色回退〕)铺满,内容居中三段。
按 1280×800 基座(2026-09-12 定)。本屏为独立屏独占呈现:屏底(深底背景色 + 石纹底噪〔迭代 2 框材组冻结件,缺位纯色回退〕)铺满,内容居中三段。
| 区 | 位置与尺寸 | 内容 |
|---|---|---|
| **顶栏语境** | 居中横排,图区上方,高约 32px | 左:关卡名(`level_row.display_name`,如「第二层 · 幽暗地域」);右:GP 常驻徽章(GP 图标+数字,直角细线数据框——GP 值域超点阵适用档,走 pattern 5 的 When-NOT-to-Use 路线);HP 归底部状态栏(2026-09-27 拍板完成) |
| **节点图区** | 居中 | 整幅节点图自绘:节点方块 + 通道折线 + 节点态着色 + Boss 紫赤描边。画布随图自适应(**冻结公式**:宽 = 70+层深×122+44.5、高 = 52+槽位×58+25+24,下限 620×256——routebug 83c11a1 根修件);通道折线**冻结 2/3/4px**(未点亮石灰 STONE/候选亮金 GOLD_LIT/悬停加粗);节点方块 52×36 内**节点图标 20px 烘焙 1:1**(♻ kenney 五键:战=弓/赏=桂冠/疗=篝火/王=王冠/商=钱袋,WARM 单色、当前金底转深墨——双色墨制)+ 编号(图内句柄 id)10px 右上角 |
| **顶栏语境** | 居中横排,图区上方,高约 32px | 左:关卡名(关卡表 `display_name` 行值,如「第二层 · 幽暗地域」);右:GP 常驻徽章(GP 图标+数字,直角细线数据框——GP 值域超点阵适用档,走 pattern 5 的 When-NOT-to-Use 路线);HP 归底部状态栏(2026-09-27 拍板完成) |
| **节点图区** | 居中 | 整幅节点图自绘:节点方块 + 通道折线 + 节点态着色 + Boss 紫赤描边。画布随图自适应(**冻结公式**:宽 = 70+层深×122+44.5、高 = 52+槽位×58+25+24,下限 620×256——路线缺陷根修后定值);通道折线**冻结 2/3/4px**(未点亮石灰 STONE/候选亮金 GOLD_LIT/悬停加粗);节点方块 52×36 内**节点图标 20px 烘焙 1:1**(♻ kenney 五键:战=弓/赏=桂冠/疗=篝火/王=王冠/商=钱袋,WARM 单色、当前金底转深墨——双色墨制)+ 编号(图内句柄 id)10px 右上角 |
| **底部状态栏** | 图区下方横排,高约 48px | 左-中:状态数据框组(直角细线,pattern 5 族)——生命(当前/上限 + 迷你血条)、属性(攻击 / 护甲 AC,有效值 + 加成角标)、本关加成(攻+X · 上限+Y · AC+Z,零加成隐藏);右端:放弃冒险按钮(危险红族文字,与状态数据框保持间距——破坏性确认异位,pattern 7) |
### Component Inventory
| 组件 | 类型 | 内容 | 交互 | 现有实现锚 |
|---|---|---|---|---|
| 关卡名标签 | 文本 | `level_row.display_name`(关卡表行读数) | 只读 | 新建(现行标题位升级) |
| GP 常驻徽章 | 直角细线数据框 | GP 图标 + 数字(`runner.gp`,冒险钱包读数;刷新经 `wallet_changed` 信号投影〔gain/buy/clear 三事件〕或写路径回执后重查) | 只读 | 新建(GP 去向拍板落地面) |
| 节点图画布 | 自绘 Control | 节点图标(五键 20px 烘焙,双色墨制)+ 编号(图内句柄 id)、通道折线、节点态着色、Boss 紫赤描边+深墨环(BOSS_PURPLE——2026-09-26 封印紫批语义:紫赤本色) | 悬停预承诺 / 点击提交选路(唯一通道) | ✅ level_map_screen.gd `_draw_graph`(视觉终稿已落码——迭代 4 T2/T3) |
| 关卡名标签 | 文本 | 关卡表 `display_name` 行值(关卡表行读数) | 只读 | 新建(现行标题位升级) |
| GP 常驻徽章 | 直角细线数据框 | GP 图标 + 数字(冒险钱包读数;刷新经钱包变动信号投影〔gain/buy/clear 三事件〕或写路径回执后重查) | 只读 | 新建(GP 去向拍板落地面) |
| 节点图画布 | 自绘 Control | 节点图标(五键 20px 烘焙,双色墨制)+ 编号(图内句柄 id)、通道折线、节点态着色、Boss 紫赤描边+深墨环(BOSS_PURPLE——2026-09-26 封印紫批语义:紫赤本色) | 悬停预承诺 / 点击提交选路(唯一通道) | ✅ 已实装(视觉终稿已落码——迭代 4 T2/T3) |
| 状态数据框组 | 直角细线数据框 ×3 | 生命(当前/上限 + 迷你血条)、属性(攻击 / 护甲 AC 有效值 + 加成角标)、本关加成(加算区增量列表,零加成隐藏) | 只读 | 新建(2026-09-27 拍板:底栏状态展示) |
| 放弃冒险按钮 | 按钮(危险红族) | 「放弃冒险(同失败结算——已获大奖励保留)」;二次确认态已拍板(见布局决策 3) | 点击(首击转确认态、二击提交) | ✅ level_map_screen.gd(可见性归组合根,仅冒险模式开) |
| 标题行 | 文本 | 停点语境提示(见布局决策 1 三停点标题) | 只读 | ✅ level_map_screen.gd `_title`(措辞已按布局决策 1 改稿) |
| 放弃冒险按钮 | 按钮(危险红族) | 「放弃冒险(同失败结算——已获大奖励保留)」;二次确认态已拍板(见布局决策 3) | 点击(首击转确认态、二击提交) | ✅ 已实装(可见性归组合根,仅冒险模式开) |
| 标题行 | 文本 | 停点语境提示(见布局决策 1 三停点标题) | 只读 | ✅ 已实装(措辞已按布局决策 1 改稿) |
**布局决策 1 —— 停点标题措辞修正(清偿状态记录 143 遗留)**:现行入口停点标题沿用「继续前进(战斗房)」,与「尚未走出第一步」的语境矛盾。三停点标题定稿:
@@ -88,7 +88,7 @@
| 单链停点 | 1 | 「继续前进」 |
| 岔路停点 | ≥2 | 「选择下一节点」 |
**布局决策 2 —— 图上直接点击选路(2026-09-27 拍板)**:候选节点的亮金描边本身是可点目标,悬停预承诺(pattern 4)后点击即提交——**候选按钮整列退役,图上点击为唯一提交通道**。实装注记:route_overlay.gd 现行候选按钮列与 `press_candidate` 探针入口随实装批拆除改造(图上命中换整图公式族命中),ui_click_probe 选路段同步改写。
**布局决策 2 —— 图上直接点击选路(2026-09-27 拍板)**:候选节点的亮金描边本身是可点目标,悬停预承诺(pattern 4)后点击即提交——**候选按钮整列退役,图上点击为唯一提交通道**。
**布局决策 3 —— 放弃冒险二次确认(2026-09-27 拍板)**:放弃 = 整局冒险落幕,不可逆(pattern 7 破坏性确认)。首击转入确认态(按钮文案变「再点一次确认放弃——不可逆」,危险红边框脉冲约 3 秒,超时回落),二击才提交。代价语义(已获大奖励保留、同失败结算)常驻按钮文案。
@@ -123,7 +123,7 @@
| **单链停点** | 兑付确认后、恰 1 个下一跳 | 标题「继续前进」;单候选节点图上点击提交(候选按钮已退役——图上点击为唯一通道) |
| **岔路停点** | 兑付确认后、≥2 个下一跳 | 标题「选择下一节点」;候选节点图上点击提交(候选按钮已退役——图上点击为唯一通道);亮金通道与候选描边逐一分叉可辨 |
| **悬停预承诺** | 悬停候选节点 | 该节点亮金描边加粗 + 所属通道增亮 + 光标切指针态;移出即回落(仅预承诺,点击才提交) |
| **放弃确认态** | 首击放弃按钮 | 文案转确认语 + 危险红边框脉冲约 3 秒;超时或移出回落;二击提交 `abandon_requested` |
| **放弃确认态** | 首击放弃按钮 | 文案转确认语 + 危险红边框脉冲约 3 秒;超时或移出回落;二击提交放弃请求 |
| **换层进入** | 上一关结算确认 → 新关开始 | 本屏以新关卡数据淡入;屏底世界侧随层进换挡(**换层色板渐变挂点**——§2.7 切片档:只动色相/饱和、明度带归一,level_index→tint 表候选值 = art-bible §6.3 三关方向,终值归色板样张批;M-2 迭代 4 落挂点) |
| **减动效** | 设置开关(设置面——迭代 8 卡④已转正,TunePanel 已退役) | 淡入、描边脉冲等全部 tween 时长减半或跳终态(UiKit.motion 单点包装,同 battle.md 通道) |
@@ -135,8 +135,8 @@
| 组件 | 动作 | 输入 | 即时反馈 | 结果(经组合根) |
|---|---|---|---|---|
| 候选节点(图上,唯一通道) | 提交选路 | 左键 | 悬停预承诺(描边加粗 + 通道增亮 + 指针)→ 点击本屏关闭 | `route_chosen(node_id)` → `LevelRunner.pick_next_node`——到达即兑付,无进入前确认步 |
| 放弃冒险按钮 | 终结整局冒险 | 左键(二击提交——首击转确认态,2026-09-27 布局决策 3) | 危险红族反馈 | `abandon_requested` → 组合根走冒险落幕(同失败结算——已获大奖励保留) |
| 候选节点(图上,唯一通道) | 提交选路 | 左键 | 悬停预承诺(描边加粗 + 通道增亮 + 指针)→ 点击本屏关闭 | 选路提交 → 关卡推进方受理——到达即兑付,无进入前确认步 |
| 放弃冒险按钮 | 终结整局冒险 | 左键(二击提交——首击转确认态,2026-09-27 布局决策 3) | 危险红族反馈 | 放弃请求 → 组合根走冒险落幕(同失败结算——已获大奖励保留) |
**零自动推进纪律**(关卡节点地图 定稿):本屏是唯一的行进发起口——不选就停在图上;没有任何定时、默认或顺延的自动前进路径。兑付后确认挂各兑付面(战斗总结结算〔settlement-summary.md〕/ 节点覆盖层 / 商店覆盖层离开动作),本屏不重复确认步。
@@ -146,13 +146,13 @@
| Data | Source System | Read / Write | Notes |
|---|---|---|---|
| 节点图结构 / 当前节点 / 已到序列 / 候选集 | LevelRunner 只读成文出口:`nodes()` / `current_node_id()` / `visited_ids()` / `next_candidate_ids()` / `node_id_of()` / `node_out_ids()` | Read | 呈现只读(关卡节点地图 验收「呈现只读」条);节点态四色直接读图 |
| GP 常驻总量 | `runner.gp`(冒险钱包) | Read | 刷新经 `wallet_changed` 信号投影(gain/buy/clear 三事件)或写路径回执后重查;冒险收束清零后本屏不可达(收束即离屏) |
| 关卡名 | `level_row.display_name`(关卡表行) | Read | 字段名实现期核对(level.tres 行键 `display_name` 在案) |
| 生命读数 | `runner.hero_hp` / `runner.effective_max_hp()`(跨战斗生命携带) | Read | 已拍板(2026-09-27):底栏状态栏承载 |
| 属性与本关加成 | `runner.effective_atk() / effective_ac() / effective_max_hp()` 与 `bonus` 统一冒险加算区(锻造/大奖励/商店购买/节点奖励同账) | Read | 已拍板:底栏承载;无推送信号——组合根打开本屏 / 兑付确认回执后重查(命令式刷新纪律,同行动经济) |
| 选路提交 | LevelRunner | **Write** | 唯一写路径 = `route_chosen` → 组合根 → `pick_next_node`;本屏零其他写面 |
| 放弃请求 | 组合根 | **Write** | `abandon_requested` → 冒险落幕流程(归冒险结构) |
| 节点图结构 / 当前节点 / 已到序列 / 候选集 | 关卡推进方只读成文出口:全节点集 / 当前节点 / 已到序列 / 候选集 / 节点编号 / 节点出边 | Read | 呈现只读(关卡节点地图 验收「呈现只读」条);节点态四色直接读图 |
| GP 常驻总量 | 冒险钱包读数 | Read | 刷新经钱包变动信号投影(gain/buy/clear 三事件)或写路径回执后重查;冒险收束清零后本屏不可达(收束即离屏) |
| 关卡名 | 关卡表 `display_name` 行值(关卡表行) | Read | 字段名实现期核对(level.tres 行键 `display_name` 在案) |
| 生命读数 | 主角当前生命 / 有效生命上限(跨战斗生命携带) | Read | 已拍板(2026-09-27):底栏状态栏承载 |
| 属性与本关加成 | 有效攻击 / 有效护甲 / 有效生命上限,以及统一冒险加算区(锻造/大奖励/商店购买/节点奖励同账) | Read | 已拍板:底栏承载;无推送信号——组合根打开本屏 / 兑付确认回执后重查(命令式刷新纪律,同行动经济) |
| 选路提交 | 关卡推进方 | **Write** | 唯一写路径 = 选路提交 → 组合根 → 关卡推进方受理;本屏零其他写面 |
| 放弃请求 | 组合根 | **Write** | 放弃意图 → 冒险落幕流程(归冒险结构) |
**架构注记**:与 battle.md 同族——UI 不拥有任何游戏状态,全部读经核心只读面,全部写经组合根;呈现层直接订阅的推进信号属 HUD框架 已锁两线之一(战斗结算明细流水 + 关卡节点地图推进信号)。
@@ -197,21 +197,21 @@
- [ ] 关卡开始即出图(停点前移语义),入口节点不自动进入;每次兑付确认后回到本屏
- [ ] 1280×800 与 16:9 全屏下顶栏/图区/动作栏无遮挡、无重叠、无内容裁切;节点图完整可见(含 Boss)
- [ ] 节点四态(已过/当前/候选/未来)+ Boss 描边在灰度截图下可分类;色值对 art-bible §4.1 色板在案
- [ ] 顶栏关卡名与 GP 常驻徽章可见、读数与 `runner` 现值一致(GP 经 gain/buy/clear 事件刷新)
- [ ] 顶栏关卡名与 GP 常驻徽章可见、读数与关卡推进方现值一致(GP 经 gain/buy/clear 事件刷新)
**交互与流程**
- [ ] 三停点标题按布局决策 1 呈现(入口停点不再出现「继续前进(战斗房)」措辞——状态记录 143 遗留清偿)
- [ ] 候选提交经组合根唯一写路径(`route_chosen` → `pick_next_node`),到达即兑付;零自动推进(无任何不选而行的路径)
- [ ] 图上点击为唯一选路提交通道(候选按钮退役,2026-09-27 拍板);悬停预承诺反馈可见;ui_click_probe 选路段随实装批改造后全绿
- [ ] 底部状态栏三组读数可见且与 runner 现值一致(生命当前/上限、攻击/AC 有效值、本关加成增量——零加成隐藏);兑付确认返回后读数刷新
- [ ] 候选提交经组合根唯一写路径(选路提交 → 关卡推进方受理),到达即兑付;零自动推进(无任何不选而行的路径)
- [ ] 图上点击为唯一选路提交通道(候选按钮退役,2026-09-27 拍板);悬停预承诺反馈可见;图上选路段随实装批改造后复核通过
- [ ] 底部状态栏三组读数可见且与关卡推进方现值一致(生命当前/上限、攻击/AC 有效值、本关加成增量——零加成隐藏);兑付确认返回后读数刷新
- [ ] 放弃按钮仅冒险模式可见(调试两层不可达);单击不提交、确认态超时回落、二击才终结(已拍板)
- [ ] 放弃后走冒险落幕(同失败结算),已获大奖励保留语义可见
**模态与测试**
- [ ] **独立性断言**:本屏激活期战斗屏宿主整体隐藏(`visible=false` 级互斥,零透出);选路提交至战斗节点后战斗屏恢复;兑付覆盖层(节点/商店)关闭后本屏恢复——场景级断言
- [ ] 场景级 headless 测试:真实实例化本屏 + 驱动选路全程 + 断言零脚本错误(ui_probe 家族扩展)
- [ ] **独立性断言**:本屏激活期战斗屏宿主整体隐藏(整屏隐藏级互斥,零透出);选路提交至战斗节点后战斗屏恢复;兑付覆盖层(节点/商店)关闭后本屏恢复——场景级断言
- [ ] 场景级 headless 测试:真实实例化本屏 + 驱动选路全程 + 断言零脚本错误(场景探针家族扩展)
- [ ] 中文渲染无豆腐块;像素字体整数倍缩放;全部操作纯鼠标可完成
---
+7 -7
View File
@@ -1,23 +1,23 @@
# 节点奖励屏 UX spec(node-reward)
> 状态:v2.9(池与交互形态同 v2.4;档名对齐四级档位框架、配额对齐现行结构锚)
> 事实源:`game/presentation/views/curio_pick_overlay.gd`(三选一覆盖层共用面)+ `main.gd`(奖励节点分支——`adventure.node_bonus_candidates/apply_node_bonus`)+ `battle_narrator.gd`(node_bonus_header)
> 状态:v2.9(现行)
> 事实源:本 spec(呈现)+ 三选一覆盖层(共用呈现件)+ 头部行文案(旁白条目)
> 相邻:[level-clear](level-clear.md)(关卡完成三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../systems/跨局循环/秘宝.md)(道具件内容权威——本批起为局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(共享池令牌库账本)
## 定位
奖励节点(reward node)到达即开的**提升三选一选择面**:**七种×档位**令牌池(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件 × 普通/优秀/稀有(+传说头奖位留空)——v2.2 起秘宝件随同调批退役出池;v2.4 起=固定配额令牌池;刷新件随迭代15② T6 入池〔优秀档 2,恢复件同批挤占收窄——adventure_table CURIO_KINDS 第七类〕),与商店货架同库同账(v2.3)、同池耗语义、同 reward_pick 封闭流。**关卡完成三选一自迭代 15② 渠道分离起候选 = 纯专长,与本面不再同池**;与关卡完成面的差异另在上下文:无部分回血、无钥石增量行,兑付后「继续」回图选路(不推进关卡)。
奖励节点(reward node)到达即开的**提升三选一选择面**:**七种×档位**令牌池(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件 × 普通/优秀/稀有(+传说头奖位留空)——v2.2 起秘宝件随同调批退役出池;v2.4 起=固定配额令牌池;刷新件随迭代15② T6 入池〔优秀档 2,恢复件同批挤占收窄——冒险表的令牌类型闭集第七类〕),与商店货架同库同账(v2.3)、同池耗语义、同奖励抽取封闭流。**关卡完成三选一自迭代 15② 渠道分离起候选 = 纯专长,与本面不再同池**;与关卡完成面的差异另在上下文:无部分回血、无钥石增量行,兑付后「继续」回图选路(不推进关卡)。
## 布局与交互
- 载体 = 提升三选一覆盖层(CurioPickOverlay——curio_pick_overlay.gd,与 [level-clear](level-clear.md) 共用组件——零新增交互原语)。
- 头部行:「关卡奖励点 / 从冒险牌库抽三选一(属性值提升与补给;与通关专长三选一同库分流):选择一件带走:」(Narrator.node_bonus_header)。
- 载体 = 提升三选一覆盖层(与 [level-clear](level-clear.md) 共用组件——零新增交互原语)。
- 头部行:「关卡奖励点 / 从冒险牌库抽三选一(属性值提升与补给;与通关专长三选一同库分流):选择一件带走:」(旁白条目)。
- 三卡点选(✓ 高亮,确认前可改选)→「确认选择」→ 兑付反馈(选中件效果 + 丢弃提示)→「继续」→ 回图选路(零自动推进同款)。
- ~~秘宝件直显/满位替换~~(v2.1 两面随池种退役——迭代 11 第①批 T7;秘宝改走营地同调装配面,见[秘宝](../systems/跨局循环/秘宝.md))。
- **三选一刷新**(v2.2 新增——P1 甲 X/Y 双源;机制现行事实源=[秘宝](../systems/跨局循环/秘宝.md)「三选一刷新双源」节,X 源承载件已随新目录批换代〔旧目录「三选一刷新件」退役→新目录「刷新练习」〕):装配「三选一刷新件」给 X 次(每次碰三选一重给、优先消耗、不结转)+ 营地「刷新升级」给 Y 次(每冒险开局注入冒险周期池、用完即无);刷新钮余量 >0 时现形,点按重抽三候选(reward_pick 既有洗牌流,池规则全保留:不放回/达上限剔牌/掷定前置直显/停点可中断)。autoplay 默认不刷(钓鱼归玩家)。
- **三选一刷新**(v2.2 新增——P1 甲 X/Y 双源;机制现行事实源=[秘宝](../systems/跨局循环/秘宝.md)「三选一刷新双源」节,X 源承载件已随新目录批换代〔旧目录「三选一刷新件」退役→新目录「刷新练习」〕):装配「三选一刷新件」给 X 次(每次碰三选一重给、优先消耗、不结转)+ 营地「刷新升级」给 Y 次(每冒险开局注入冒险周期池、用完即无);刷新钮余量 >0 时现形,点按重抽三候选(奖励抽取既有洗牌流,池规则全保留:不放回/达上限剔牌/掷定前置直显/停点可中断)。自动演示默认不刷(钓鱼归玩家)。
## 口径
- **同库同池耗(v2.4)**:池=固定配额令牌库(档位=四级框架 普通/优秀/稀有/传说,传说=头奖位留空〔稀有档现投放为行动点件〕;各 kind 现行配额读 `game/data/tables/adventure.tres`——「道具件 2」原列稀有档,随 aux_item 池种退役〔迭代 11 第①批 T7,落地晚于追认 b〕收窄——状态记录自决披露项);洗牌不放回抽 3(**同 kind 去重每 kind 至多一枚**——「三种不同选项」体验延续,kind 出现率∝剩余令牌数)、选中踢池未选回池、池耗即枯竭(余库不足出齐剩余,空库=零候选合法态直通);**种类次数上限退役**(池消耗即上限)。数值对:小GP 20/大GP 50、小增伤 +1/大 +2、小护甲 +1/大 +2、小生命 +5/大 +10、恢复 +6 当即回血(封顶生效上限)、AP +1。**MC 策略 316-f**:1 级优先度=最稀有(legendary>rare>excellent>common,头奖位现行留空)、2 级=最左(并列同档取最左)。配额结构锚与档位构成读冒险表(schema 硬断言配额结构锚——见 `data/schemas/adventure_table.gd`)。**v2.3 第三渠道并账维持**:商店货架=同库付费渠道(令牌级货架,买一件=奖励面少一件),库存=剩余令牌数,账本语义与切边注记归[冒险结构](../systems/冒险循环/冒险结构.md)共享池账本节。
- **同库同池耗(v2.4)**:池=固定配额令牌库(档位=四级框架 普通/优秀/稀有/传说,传说=头奖位留空〔稀有档现投放为行动点件〕;各 kind 现行配额读 `game/data/tables/adventure.tres`——「道具件 2」原列稀有档,随 aux_item 池种退役〔迭代 11 第①批 T7,落地晚于追认 b〕收窄——状态记录自决披露项);洗牌不放回抽 3(**同 kind 去重每 kind 至多一枚**——「三种不同选项」体验延续,kind 出现率∝剩余令牌数)、选中踢池未选回池、池耗即枯竭(余库不足出齐剩余,空库=零候选合法态直通);**种类次数上限退役**(池消耗即上限)。数值对:小GP 20/大GP 50、小增伤 +1/大 +2、小护甲 +1/大 +2、小生命 +5/大 +10、恢复 +6 当即回血(封顶生效上限)、AP +1。**MC 策略 316-f**:1 级优先度=最稀有(legendary>rare>excellent>common,头奖位现行留空)、2 级=最左(并列同档取最左)。配额结构锚与档位构成读冒险表(结构校验归实现侧)。**v2.3 第三渠道并账维持**:商店货架=同库付费渠道(令牌级货架,买一件=奖励面少一件),库存=剩余令牌数,账本语义与切边注记归[冒险结构](../systems/冒险循环/冒险结构.md)共享池账本节。
- ~~道具件入本关库存~~(v2.2 退役——秘宝局内不掉落机制道具,三轴 doctrine 锁死)。
- ~~「池空直通」合法态~~(v2.4 随令牌池换代退役又回归异形:旧口径=GP 无上限保证牌库恒非空;现口径=固定配额池(现行值读冒险表)真可耗尽,空库=零候选直通合法态——池耗即枯竭既定后果)。
+8 -8
View File
@@ -1,7 +1,7 @@
# 结算总结 UX spec(settlement-summary)
> 状态:v1.4(2026-10-26 迭代 37 结项:事实源行号引用改指拆件后现址,呈现设计与规则零变化;承 v1.3(2026-10-15 简明技术中文用语规范(用户令)批:词面修正 8 处——两面分节、纯呈现轻量 spec 不变;无新概念))
> 事实源:`game/presentation/views/battle_end_overlay.gd`(胜利金蜡封 / 失败暮蓝双形态载体)+ `main.gd`(胜利接线:调试 `main.gd:455-461`、冒险主路径 `main_adventure_flow.gd:165`;冒险终局接线:`main_adventure_flow.gd:19` 的 `adventure_finished.connect`)
> 状态:v1.4(现行)
> 事实源:本 spec(呈现)+ 战斗终局覆盖层(胜利金蜡封 / 失败暮蓝双形态)
> 相邻:[battle.md](battle.md)(战斗屏出口锚与信息归属两原则)/ [level-clear.md](level-clear.md)(关卡结算屏——Boss 胜利走大奖励面,非本 spec)/ [level-map.md](level-map.md)(GP 常驻徽章)
## 定位
@@ -15,17 +15,17 @@
## 战斗总结结算(战斗胜利面)
- 触发:冒险模式战斗胜利(verdict=win)含 Boss 胜利——迭代 10 起 Boss 胜利也经本面(钥石逐杀掉落增量在此呈现,确认后进[关卡结算屏](level-clear.md)三选一面;原「Boss 胜利走大奖励面不经本面」口径随大奖励重构退役)。非 Boss 接线 2026-09-29 迭代 8 卡⑥。
- 载体:battle_end_overlay 胜利形态(金蜡封)。
- 触发:冒险模式战斗胜利(判胜)含 Boss 胜利——迭代 10 起 Boss 胜利也经本面(钥石逐杀掉落增量在此呈现,确认后进[关卡结算屏](level-clear.md)三选一面;原「Boss 胜利走大奖励面不经本面」口径随大奖励重构退役)。非 Boss 接线 2026-09-29 迭代 8 卡⑥。
- 载体:战斗结束覆盖层胜利形态(金蜡封)。
- 呈现内容(2026-09-27 拍板面 + 迭代 10 扩):金蜡封 + 判定行 + 战利品行(GP 增量 + 总量;Boss/精英击杀追加**钥石增量行**——首杀/复杀档位读 meta.tres boss_keystone)+「下一节点」钮(明细回看入口已随 2026-10-04 P-C7 退役——冒险中回看归「生涯」)。
- 数据锚:增量 = `battle_finished` 载荷 `gp_dropped`;总量 = runner.gp 现值(LevelRunner 入账先于呈现连接——次序依赖在案,M-1 迭代 2 T5 接线)。
- 数据锚:增量 = 战斗结束信号的掉落载荷;总量 = 关卡推进方 GP 现值(关卡推进方入账先于呈现连接——次序依赖在案,M-1 迭代 2 T5 接线)。
- 出口:「下一节点」→ 关卡画面选路(零自动推进)。
## 冒险总结结算(冒险落幕面)
- 触发:主角倒下(verdict=lose)/ 末关完成 / 玩家放弃——三结局同面(`adventure_finished` 收束)。
- 载体:与战斗面同一个 battle_end_overlay(2026-09-29 迭代 6 T7 接线)——末关通关走 `open_victory`(金蜡封 +「本局 GP 入账 · 合计 N」行,GP 收束清零前的呈现侧影子账);倒下 / 放弃走 `open_defeat`(暮蓝)。无独立「冒险终局面板」实体,待办清单状态记录旧称随本条完成。
- 呈现内容:冒险摘要五件事(定义归[冒险结构](../systems/冒险循环/冒险结构.md))+ 钥石写入明细(= 本局逐杀 Boss 掉落合计;消费归[元进度](../systems/跨局循环/元进度.md) settle_adventure——迭代 10 起大奖励面不再产钥石)+ GP 合计行。
- 触发:主角倒下(判负)/ 末关完成 / 玩家放弃——三结局同面(由冒险收束信号统一触发)。
- 载体:与战斗面同一个战斗结束覆盖层(2026-09-29 迭代 6 T7 接线)——末关通关走胜利态开屏(金蜡封 +「本局 GP 入账 · 合计 N」行,GP 收束清零前的呈现侧影子账);倒下 / 放弃走失败态开屏(暮蓝)。无独立「冒险终局面板」实体,待办清单状态记录旧称随本条完成。
- 呈现内容:冒险摘要五件事(定义归[冒险结构](../systems/冒险循环/冒险结构.md))+ 钥石写入明细(= 本局逐杀 Boss 掉落合计;消费归[元进度](../systems/跨局循环/元进度.md) 的冒险结算入账——迭代 10 起大奖励面不再产钥石)+ GP 合计行。
- 出口:确认 → 回存档首页。
## 口径
+3 -3
View File
@@ -1,7 +1,7 @@
# 商店覆盖层 UX spec(shop)
> 状态:v1.1(2026-09-30 审计拍板点 4B 用户裁决写入文档——甲+乙并行:编排面小节落关卡节点地图、呈现面独立成文)
> 事实源:`game/presentation/views/shop_overlay_view.gd`(ShopOverlay)
> 状态:v1.1(现行)
> 事实源:本 spec(呈现)+ 商店覆盖层(呈现件)
> 机制权威:[关卡节点地图](../systems/冒险循环/关卡节点地图.md)(商店房编排:每关恰 1/Boss 前一位必经/兑付触发);定价数值归[难度曲线与Boss](../systems/冒险循环/难度曲线与Boss.md)(shop.tres,provisional)
## 定位
@@ -11,7 +11,7 @@
## 布局与交互
- 模态覆盖层(dim + 居中):标题「商店」/ GP 余额读数/ 令牌级货架卡(进店自共享池抽 3 张:显示名·效果·现价——六行池面含回复与专长凭证;买一件 = 池少一件、货架随即全架重抽)/ 购买按钮(余额不足禁用置灰——pattern 8 禁用态即理由)/ 刷新按钮(GP 付费刷新,价 = ceil(20×1.8^本次进店已付费刷) provisional,余额不足禁用)/ 离开按钮。
- 购买意图 `buy_requested` 经组合根转发 runner(pool 模式载荷 = kind_id);成功后货架整架重建(买 1 全重抽反馈)。刷新意图 `refresh_requested` 经组合根转发 presenter。
- 购买意图经组合根转发(pool 模式载荷 = kind_id);成功后货架整架重建(买 1 全重抽反馈)。刷新意图经组合根转交商店呈现件受理。
- 离开 → 关卡画面兑付确认回选路(零自动推进)。
- 刷新与货架口径(2026-10-04 P-C2+Q2 裁决,SR-1 落码):基础免费 0 次 + GP 付费刷新 + 池源同源(与三选一同一令牌库本)——原「无独立刷新、全陈列」裁决随该批被推翻;天赋侧「刷新次数」增益预留 free_count 位(待办清单登记条在册)。
+11 -11
View File
@@ -1,7 +1,7 @@
# 术语表
> 全库专用术语的唯一对照表(2026-10-11 用户令建立)。规矩:新术语先登记本表、再在文档使用;退役词见文末对照表,文档正文一律使用现行词。文档中遇到看不懂的词,查本表。用词纪律(禁用词、替换值、豁免范围)见「用语规范」节。
> 状态:**现行 v1.24**——2026-10-11 建立;历次升版明细见 git 提交史。
> 状态:**现行 v1.25**——2026-10-11 建立;历次升版明细见 git 提交史。
## 格与棋盘
@@ -12,18 +12,18 @@
| 安全格 | 没有惩罚的格子——挖开安全并获得其上信息与收益 |
| 整图 | 单张棋盘(的整个范围)——原「整图」退役(2026-10-11 用户裁)。例:整图叠加层、整图作用域 |
| 叠加层(整图叠加层) | 秘宝产生的信息的自持容器:按效果分类的格集,随图消亡、不回写棋盘(见[秘宝](systems/跨局循环/秘宝.md)) |
| 封印 / 控制 | 封印动作的机制名/玩家名:封印印记落在危险格上为「标中」(危险格被控制、化险为利),封错为「标空」(原「旗 / 控制」,2026-10-18 用户裁:「旗」概念不存在——动作=封印、印记=封印印记);代码契约键与标识符统一用 `seal_*`(原 `flag_*` 全族换代),板面单字字形=「封」 |
| 封印 / 控制 | 封印动作的机制名/玩家名:封印印记落在危险格上为「标中」(危险格被控制、化险为利),封错为「标空」(原「旗 / 控制」,2026-10-18 用户裁:「旗」概念不存在——动作=封印、印记=封印印记),板面单字字形=「封」 |
| 真视 | 直接告诉玩家某范围内哪些格是危险格的效果(秘宝承重契约:一次最多 3×3,见[秘宝](systems/跨局循环/秘宝.md)核心规则) |
| 类型微光 | 棋盘上按格子类型显示的提示光效(视野参数控制其显示范围) |
| 换图 / 弃图刷新 | 清完一图进入下一图 / 花储备放弃当前图重开一张(代价见[行动经济](systems/排雷循环/行动经济.md)) |
| 装板(装板期) | 把地图战斗资源摇定的类型身份铺设到棋盘格子上的布置动作(阶段);由战斗结算的布置段执行,随机取数标签为 `variant_place` |
| 摇号 / 摇定 | 按池配置用随机数决定结果(资源身份、遭遇抽取、货架抽签);随机服务实现见 `game/core/rng_service.gd`(流标签为代码事实) |
| 摇号 / 摇定 | 按池配置用随机数决定结果(资源身份、遭遇抽取、货架抽签);随机服务为全库单点(流标签为既有事实,归属见架构文档) |
## 关卡与地图
| 术语 | 定义 |
|---|---|
| 信息迷雾 | 关卡节点图上按邻近层数隐藏节点类型的可见性机制——只隐藏类型,结构(节点、连线、入口、终点位置)恒可见;与「视野迷雾」(战斗棋盘按英雄曼哈顿半径遮挡格子)同词根易混,代码标识符一律不用 `fog_` 前缀。参数 N=信息可见层数(`level.tres` 的 `info_visible_layers`),机制见[关卡节点地图](systems/冒险循环/关卡节点地图.md) |
| 信息迷雾 | 关卡节点图上按邻近层数隐藏节点类型的可见性机制——只隐藏类型,结构(节点、连线、入口、终点位置)恒可见;与「视野迷雾」(战斗棋盘按英雄曼哈顿半径遮挡格子)同词根易混。参数 N=信息可见层数(`level.tres` 的 `info_visible_layers`),机制见[关卡节点地图](systems/冒险循环/关卡节点地图.md) |
## 系统资源与经济
@@ -67,7 +67,7 @@
| 魔法护甲 | 三型甲之一(= 现役甲域 ward,基础 AC 最低、被普通攻击克);「魔法甲」系笔误、非别名,不单独登记 |
| 并联双通道 | 待解锁路线的解锁结构:练满前一档 **OR** 一次性解锁价,两通道互不影响、玩家自选其一、不设串联门槛 |
| 出场选择 | 一次冒险 = 一个武器类型 + 一个甲型;营地内免费切换、冒险开始后锁死 |
| 路线 id / 存档域键名 | 锻造工坊实现层词汇(非玩家可见,2026-10-13 迭代 23 反哺落文登记):六路线 id = sword/spear/staff/light/heavy/ward(甲路线 id = 甲型值本身);存档域 `forge_routes = {levels〔六路线等级〕, unlocked〔直购解锁清单〕, weapon/armor〔当前选中〕}`——初始持有与练满前一档为派生解锁不落档(派生单点 = forge_route_unlocked) |
| 路线 id / 存档域键名 | 锻造工坊实现层词汇(非玩家可见,2026-10-13 迭代 23 反哺落文登记):六路线 id = sword/spear/staff/light/heavy/ward(甲路线 id = 甲型值本身);存档域 `forge_routes = {levels〔六路线等级〕, unlocked〔直购解锁清单〕, weapon/armor〔当前选中〕}`——初始持有与练满前一档为派生解锁不落档(派生单点 = 解锁判定) |
## 道具体系
@@ -94,7 +94,7 @@
| 术语 | 定义 |
|---|---|
| `seal_missed` | 封印落空事件(原 `flag_missed` 换代):封印动作落在安全格上时发出,订阅面据此做限次与豁免判定(迭代 31 卡② 落地) |
| `seal_missed` | 封印落空事件(旧名已换代):封印动作落在安全格上时发出,订阅面据此做限次与豁免判定(迭代 31 卡② 落地) |
| `forgive_seal_miss` | 本场首次封印落空豁免——专长「完美保险」的效果类型,`domain=battle` 首例;有效误标=误标总数 − 豁免数,豁免不改写误标账(迭代 31 卡② 落地) |
## 美术表现
@@ -120,9 +120,9 @@
## 用语规范(2026-10-15 用户令立)
词面纪律的登记处。通用禁用词与选词以**简明技术中文(STC)词表**为权威:外部技能文件 `C:\Users\DK\.zcode\skills\simplified-technical-chinese\词表.md`,约 170 条,配检查脚本 check.py。
词面纪律的登记处。通用禁用词与选词以**简明技术中文(STC)词表**为权威:外部同名词表,约 170 条,配检查脚本。
本仓库的机器闸 = `tools/doc_term_audit.py`,已吸收本节项目裁项。**词表冻结**:本节与该闸的禁用词表不再扩充,扩表唯一入口=用户实读反馈并经用户裁决(由工程/文档设计改动批写入,agent 禁自行增词)。本节只登记两类裁项:A 已裁替换、B 豁免条款。历史退役词(雷格/正主/落籍等 7 组)见下方「退役词对照」节,不与 A 表重复。
本仓库的机器闸 = 用语审计(闸件清单唯一出处=process-conventions 运行与验证节),已吸收本节项目裁项。**词表冻结**:本节与该闸的禁用词表不再扩充,扩表唯一入口=用户实读反馈并经用户裁决(由工程/文档设计改动批写入,agent 禁自行增词)。本节只登记两类裁项:A 已裁替换、B 豁免条款。历史退役词(雷格/正主/落籍等 7 组)见下方「退役词对照」节,不与 A 表重复。
### A. 已裁替换(写新文本必须用「用」列)
@@ -148,7 +148,7 @@
1. **历史文本**:git 提交说明、裁决记录中引用用户原话的句子——保留原用词,不按 A 表追改。
2. **对照性文本**:「退役词对照」表本身、「原 ××」式更名记载、禁令引举句——旧词在这些位置是内容本身,不是残留。
3. **玩家可见专名与机制词**:按「对外命名纪律」节与各系统文档现行用语使用(标中/踩雷等机制词在文档内部照常用)。
4. **被检查的禁字不算使用**:守卫代码里作为检查对象的禁字字面量——`game/tests/unit/core/adventure_feats_test.gd` 的 `contains("旗")` 检查项、`game/tests/unit/presentation/ui_font_asset_test.gd` 的字形集负探针、`game/data/tables/feats.tres` 头注的「desc 禁『雷/旗』字面」、`tools/doc_term_audit.py` 的退役词表定义行——不属换代范围:被检查的字不是对该字的使用,改写等于自毁守卫。术语清扫的复扫口径按 **game/ 全部文件类型、不排除任何扩展名**执行(迭代 31 卡① 实测:金币/插旗/雷格/惩罚格/`flag` 均 0 命中,残留仅为上述保留面)。
4. **被检查的禁字不算使用**:守卫里作为检查对象的禁字字面量——核心层检字断言所检查的禁字本身、呈现层字形集负探针、`game/data/tables/feats.tres` 头注的「desc 禁『雷/旗』字面」、用语审计闸的退役词表定义行——不属换代范围:被检查的字不是对该字的使用,改写等于自毁守卫。术语清扫的复扫口径按 **game/ 全部文件类型、不排除任何扩展名**执行(迭代 31 卡① 实测:金币/插旗/雷格/惩罚格/`flag` 均 0 命中,残留仅为上述保留面)。
存量口径:A 表生效前已入库的正文不做强制清扫;文件后续被批次触碰时,顺带按 A 表换代。
@@ -168,11 +168,11 @@
| 插旗 | 封印(2026-10-18 用户裁:封印=正式动作名,全库统一,非比喻非「对外表达」别名) |
| 旗(游戏概念语境) | 封印印记(2026-10-18 用户裁:「旗」概念不存在——封印动作落下的印记称封印印记) |
| flag_*(代码契约键/标识符) | seal_*(2026-10-18 用户裁全族换代;迭代 31 卡① 已清零) |
| 系统级断言套件 / 冒烟全量 | 系统测试(2026-10-22 用户裁——标准四级测试层之一:启动真实工程走真实玩法路径的断言面;依据=提案正文 §二)(《提案-测试术语与层级重构方案》已退役,考古=git 历史——删除批 53075baa,删除前全稿见历史版本 b4da36a8) |
| 系统级断言套件 / 冒烟全量 | 系统测试(2026-10-22 用户裁——标准四级测试层之一:启动真实工程走真实玩法路径的断言面;依据=提案正文 §二)(《提案-测试术语与层级重构方案》已退役,考古=git 历史——删除批与删除前全稿均可在其中查得) |
| 单元套件 | 单元测试(2026-10-22 用户裁——标准四级测试层之一,「套件」单独成词非必要) |
| 启动探针 / 秒级冒烟探针 / L0–L3 | 冒烟测试(2026-10-22 用户裁——极小启动场景:能否启动、能否加载表、能否进主屏,零逻辑判定) |
| 轻闸 / 重闸 / 三查 | (随 2026-10-22 频率规则消亡,无替代词——检出工作区零测试;卡内只测自己改的东西;全量只在收尾与合回两个时机。机制词「去重闸」含「重闸」二字但与此无关,不在退役之列) |
| 魔法道具 | 秘宝(2026-10-26 用户裁入表:AUXILIARY_ITEMS 旧中文规范名,2026-10-05 已更名) |
| 魔法道具 | 秘宝(2026-10-26 用户裁入表:秘宝系统旧中文规范名,2026-10-05 已更名) |
| 罕见(档位名) | 稀有(2026-10-26 用户裁入表:旧三档档位名,现行四级第三档=稀有;散文形容词义不受限) |
| 通关 | 关卡完成(单关)/冒险成功(整局)——2026-10-26 用户裁:主体明确短语族替代歧义单义词 |
| 冒险终结 | 冒险落幕——2026-10-26 通关口径统一批 |
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

+40 -22
View File
@@ -30,10 +30,7 @@ var _rap := 0
var rap: int:
get: return _rap
var _last_round_end: Dictionary[String, String] = {} # {path_tag, continuation_hint}——排雷玩法 组包 round_end 的合法数据源
var last_round_end: Dictionary[Variant, Variant]:
get: return _last_round_end
var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
var _pending_ap_delta := 0 # 下轮行动点修正排队(action 型 ±1 排队,轮开始兑现后清零)
# 蓄力流运行态:转蓄力=round_end grant_ap(mode:transfer) 订阅(finish_round_stop 定夺)、
# 深挖对价=dig_paid counter_spend 订阅(request_dig 施加面);charge_layers=蓄力层账本
@@ -44,7 +41,6 @@ var charge_layers: int:
var _fx: EffectEngine = null
var _ctx: EffectContext = null
var _last_stop_ap := 0 # 停手侧剩余 AP 记账(转存决策在 round_end 发射点——grant_ap(transfer) 订阅定夺)
var _ap_exhausted_last := false # 上轮 AP 耗尽封印(一滴不剩 round_start when 面)
func set_fx(fx: EffectEngine, ctx: EffectContext) -> void:
@@ -66,7 +62,7 @@ func _phase_text(p: int) -> String:
func _cost(kind: String) -> int:
var ae = db.action_economy
var ae = db.run.action_economy
match kind:
"dig":
return ae.dig_ap_cost # 基础挖格成本恒表值(钻头对价挂付费开掘侧)
@@ -97,11 +93,29 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
_reject("phase")
return false
var cost := _cost("dig")
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前:
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前——施加单点 _apply_dig_paid_hits):
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。
# counter_add〔双管镐 付费挖格计数〕/ grant_ap〔每第 2 次返 1 AP——对价减免,
# mod_eq 闸在行 when〕同面施加)
var apply_res := _apply_dig_paid_hits(cost)
cost = int(apply_res["cost"])
if _ap < cost:
_reject("resource_face")
return false # 拒绝轮零入池(矿脉储能只在扣费受理后兑付——C2 成功才登记同款纪律)
if int(apply_res["refund"]) > 0:
cost = maxi(0, cost - int(apply_res["refund"])) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
var accepted := _spend_cost_and_arbitrate(cost, clears_map)
if accepted and int(apply_res["rap_gain"]) > 0:
grant_reserve_ap(int(apply_res["rap_gain"])) # 每次受理的付费挖格产 RAP(一次性入池——每动作恰一挖,连锁不二次)
return accepted
# dig_paid 命中施加单点(迭代 46 卡A T3 自 request_dig 迁出——分支本体逐字保留:
# counter_spend 加收对价并积计数/counter_add 施加〔双管镐 付费挖格计数〕/
# grant_ap 对价减免〔每第 2 次返 1 AP——mod_eq 闸在行 when〕/grant_reserve_ap 矿脉储能累计;
# 命中序=声明序不变。返回 {cost, refund, rap_gain} 交受理面消费)
func _apply_dig_paid_hits(base_cost: int) -> Dictionary:
var cost := base_cost
var dig_refund := 0
var rap_gain := 0
if _fx != null:
for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
if String(h.get("kind", "")) == "counter_spend":
@@ -114,12 +128,9 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
EffectEngine.counter_add(_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
elif String(h.get("kind", "")) == "grant_ap":
dig_refund += int(h.get("value", 0))
if _ap < cost:
_reject("resource_face")
return false
if dig_refund > 0:
cost = maxi(0, cost - dig_refund) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
return _spend_cost_and_arbitrate(cost, clears_map)
elif String(h.get("kind", "")) == "grant_reserve_ap":
rap_gain += int(h.get("value", 0))
return {"cost": cost, "refund": dig_refund, "rap_gain": rap_gain}
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
@@ -200,13 +211,15 @@ func finish_round_stop(transfer: bool, transfer_pct: int = 100) -> void:
# 轮内刷新:唯一入口 =
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
func request_refresh() -> bool:
# cost_override=折后价入参(迭代 46 卡A T3——BattleRefreshFlow 折价读数传入;缺省 -1=表基线逐位不变)
func request_refresh(cost_override: int = -1) -> bool:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
if _rap < db.action_economy.rap_refresh_cost:
var cost := int(db.run.action_economy.rap_refresh_cost) if cost_override < 0 else cost_override
if _rap < cost:
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
_rap -= db.action_economy.rap_refresh_cost
_rap -= cost
return true
@@ -216,7 +229,6 @@ func notify_settlement_done(next_round_mode: String) -> bool:
_reject("phase")
return false
_settlement_consumed = true
_next_round_mode = next_round_mode
return true
@@ -249,7 +261,7 @@ func notify_round_start() -> bool:
else:
_reject("phase")
return false
_ap = maxi(0, db.action_economy.ap_per_round + _pending_ap_delta) # 每轮基线 + 排队修正(下限 0)
_ap = maxi(0, db.run.action_economy.ap_per_round + _pending_ap_delta) # 每轮基线 + 排队修正(下限 0)
_pending_ap_delta = 0
_phase = Phase.ACTIVE
_settlement_consumed = false
@@ -272,13 +284,19 @@ func _finish_round(path_tag: String, continuation_hint: String) -> void:
assert(path_tag != "ap_exhausted" or _ap == 0, "耗尽轮剩余行动点恒为 0(实现层断言兜底)")
assert(path_tag != "cleared" or _ap == 0, "清图轮剩余行动点当场清零(不进储备池)")
_last_round_end = {"path_tag": path_tag, "continuation_hint": continuation_hint}
_ap_exhausted_last = path_tag == "ap_exhausted" # 一滴不剩 when 面(轮始发射读数)
_phase = Phase.WAITING
# 上轮 AP 是否耗尽(round_start 发射 payload 读数面;轮始兑现后保持到本轮末)
# 上轮组包读数({path_tag, continuation_hint}——排雷玩法 组包 round_end 的合法数据源;
# 属性→方法 K5 换形,消费方 minesweeper_rules 单点改址)
func last_round_end() -> Dictionary[String, String]:
return _last_round_end
# 上轮 AP 是否耗尽(round_start 发射 payload 读数面;轮始兑现后保持到本轮末)——
# 派生读(同源同刻:_last_round_end 组包点即派生原点,K5 派生合并——存储成员退役)
func ap_exhausted_last_round() -> bool:
return _ap_exhausted_last
return _last_round_end.get("path_tag", "") == "ap_exhausted"
func _reject(source: String) -> void:
+65 -235
View File
@@ -27,19 +27,14 @@ var levels_total := 0 # 序列总关数(读 level 表行
var master_seed := 0 # 本局主种子(摘要第五件事)
var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成)
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 _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 件——停点三态/刷新三源/凭证出箱/信号面)----
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收)
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
var _bp_store: BreakpointStore # 断点组装与回放件(_init 建;六成员+27 键登记单源+读写侧机组随件——宿主读写经件)
var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面)
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
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)
_loadout = AttuneLoadout.new(db, _attunement)
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})
# 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块)
@@ -105,9 +102,9 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_stats.armor_type = String(params.get("armor_type", ""))
summary = {}
_reset_curio_state()
levels_total = db.level_count() # 表访问收口(关数读 level 表行数)
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
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 对照零扰动)
_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
@@ -115,12 +112,12 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
_bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
for iid_v2 in _attunement:
var rp_row: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v2))
var rp_row: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v2))
if (rp_row.get("effect_kinds", []) as Array).has("refresh_stock_grant"):
_refresh_bank.add_refresh_stock(int(rp_row.get("magnitude", 0)))
# 特性开局注入(MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派
@@ -152,99 +149,56 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
return false
master_seed = int(bp.get("master_seed", 0))
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
_resume_ledger(bp, params)
_resume_curio_state(bp)
_resume_params(bp, params)
_resume_counters(bp)
# ---- 断点回放(R2 整族归件——件侧语句入 _bp_store、宿主侧语句按原语句序留本壳,序逐步保形)----
_bp_store.restore_ledger(bp, params) # 续玩账面回填——主角账三件片段各还原各键(件侧;clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
summary = {}
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_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
if not bp_pending.is_empty():
_pending_next_level = true
_bp_store._pending_next_level = true
_state = State.RUNNING
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
return true
if not bp_result.is_empty():
_pending_next_level = true
_bp_store._pending_next_level = true
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
_state = State.RUNNING
return true
_resume_bp = bp.duplicate(true)
_bp_store._resume_bp = bp.duplicate(true)
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
_loadout_seeded = false
_bp_store._loadout_seeded = false
_loadout.set_attunement(_attunement)
adventure_inventory.reset()
_state = State.RUNNING
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.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.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:
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
_stop.reset_adventure_state()
_loadout_seeded = false
_bp_store._loadout_seeded = false
_stats.feats = []
adventure_counters = {}
@@ -263,7 +217,7 @@ func effective_max_hp() -> int:
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
func set_save_manager(sm: SaveManagerCore) -> void:
_save_manager = sm
_bp_store._save_manager = sm
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
@@ -278,13 +232,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int:
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
func continue_to_next_level() -> bool:
if not _pending_next_level:
if not _bp_store._pending_next_level:
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
return false
if _stop.curio_stop_pending():
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
return false
_pending_next_level = false
_bp_store._pending_next_level = false
_stop.clear_resolved()
level_index += 1
return _start_current_level()
@@ -330,7 +284,7 @@ func present_node_curio(candidates: Array) -> void:
func _attune_refresh_x_total() -> int:
var total := 0
for iid_v in _attunement:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(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("bonus_refresh"):
total += int(arow.get("magnitude", 0))
return total
@@ -343,9 +297,9 @@ func _loadout_on_level_inventory() -> void:
var inv = _runner.treasure_inventory
if inv == null:
return
if not _loadout_seeded:
_loadout_seeded = true
var bp_charges: Variant = _bp_read("attunement_charges", _resume_bp, null) if not _resume_bp.is_empty() else null
if not _bp_store._loadout_seeded:
_bp_store._loadout_seeded = true
var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null
if bp_charges is Dictionary:
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
else:
@@ -368,9 +322,9 @@ func _loadout_on_level_inventory() -> void:
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
func _boss_keystone_drop(li: int) -> void:
var first := true
if _save_manager != null:
first = not _save_manager.boss_first_kill_marked(li)
var row: Dictionary[Variant, Variant] = db.meta_boss_keystone_row(li)
if _bp_store._save_manager != null:
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 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}) # 逐杀明细+累计单写原语
@@ -419,9 +373,9 @@ func _start_current_level() -> bool:
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
var resume_state: Dictionary[Variant, Variant] = {}
if not _resume_bp.is_empty():
resume_state = {"node_id": int(_bp_read("node_id", _resume_bp, -1)),
"visited_ids": _bp_read("visited_ids", _resume_bp, []) as Array}
if not _bp_store._resume_bp.is_empty():
resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)),
"visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array}
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
@@ -431,7 +385,7 @@ func _start_current_level() -> bool:
if ok:
_emit_level_started()
_present_level_entry(resume_state)
_resume_bp = {}
_bp_store._resume_bp = {}
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:
_runner.present_entry_choice()
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()
@@ -464,7 +418,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if state != "running":
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
return
_last_level_payload = payload.duplicate(true)
_bp_store._last_level_payload = payload.duplicate(true)
var result := String(payload.get("result", ""))
if result == "defeat":
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
@@ -473,7 +427,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if result != "victory":
push_error("[冒险结构] 关卡结束结果出域: %s" % result)
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_keystone_drop(level_index)
if level_index < levels_total:
@@ -481,7 +435,7 @@ func _on_level_finished(payload: Dictionary) -> void:
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
_pending_next_level = true
_bp_store._pending_next_level = true
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
stop_base["gp"] = _wallet.gp
_stop.open_level_clear(stop_base)
@@ -499,7 +453,7 @@ func _settle(result: String) -> void:
"stopped_at": 0 if result == "completed" else level_index,
"keystone_total": _journal.keystone_gained(),
"master_seed": master_seed,
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
"aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
@@ -507,19 +461,19 @@ func _settle(result: String) -> void:
}
# 败退明细(数值验证工具取数面——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"] = {
"level_index": level_index,
"node_id": int(_last_level_payload.get("node_id", -1)),
"node_type": String(_last_level_payload.get("node_type", "")),
"node_id": int(_bp_store._last_level_payload.get("node_id", -1)),
"node_type": String(_bp_store._last_level_payload.get("node_type", "")),
}
if _runner != null:
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
if _save_manager != null:
_save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
_save_manager.write_to_disk()
if _bp_store._save_manager != null:
_bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
_bp_store._save_manager.write_to_disk()
_state = State.SETTLING
adventure_finished.emit(summary)
_state = State.IDLE
@@ -530,131 +484,7 @@ func _on_level_choices_presented(_candidates: Array) -> void:
_snapshot_breakpoint()
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
# 键序即快照键序(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 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
}
# ---- 断点快照委托(组装与读侧策略机组归 breakpoint_store 件——六成员+27 键登记单源随件;
# 本壳一行转发:CurioStop 构造注入的 Callable 目标与本类 _on_level_choices_presented 调用点零改)----
func _snapshot_breakpoint() -> void:
if _save_manager == null or state != "running":
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))
_bp_store.snapshot(state, level_index, master_seed, _runner, adventure_counters)
+3 -3
View File
@@ -33,7 +33,7 @@ func item_ids() -> Array:
func seed_for_adventure(inventory: TreasureItems) -> void:
for iid_v in _ids:
var iid := String(iid_v)
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(iid)
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid)
if String(arow.get("form", "")) != "active_charged":
continue
_ensure_rows(iid, maxi(0, int(arow.get("charges_per_adventure", 0))), inventory)
@@ -44,7 +44,7 @@ func seed_for_adventure(inventory: TreasureItems) -> void:
func reset_cycle(cycle: String, inventory: TreasureItems) -> void:
for iid_v in _ids:
var iid := String(iid_v)
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(iid)
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid)
if String(arow.get("form", "")) != "active_charged":
continue
if String(arow.get("charge_reset", "adventure")) != cycle:
@@ -94,7 +94,7 @@ func breakpoint_fragment(inventory: TreasureItems) -> Dictionary[Variant, Varian
var charges: Dictionary[Variant, Variant] = {}
for iid_v in _ids:
var iid := String(iid_v)
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(iid)
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(iid)
if String(arow.get("form", "")) == "active_charged":
charges[iid] = charges_left(iid, inventory)
return {"attunement_charges": charges}
+1 -1
View File
@@ -12,6 +12,6 @@ static func slots(db: ConfigDb, save: Variant) -> int:
return 0
var n := 0
for i in range(1, 4):
if save.tree_level("att_socket%d" % i) >= 1:
if save.meta_state.tree_state().nodes.get("att_socket%d" % i, 0) >= 1:
n += 1
return n
+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
+1 -1
View File
@@ -175,5 +175,5 @@ func _fx_fact(fact: String) -> Variant:
# 基线 = enc 口径,-1(表值兜底态)时解析为表值后叠加,守恒口径与 begin_battle 注入一致)
func _sync_hero_ac() -> void:
if int(ac.base) < 0:
ac.base = int(db.hero.armor_class)
ac.base = int(db.combat.hero.armor_class)
damage.set_hero_ac_override(int(ac.base) + int(ac.round) + int(ac.battle))
+114 -347
View File
@@ -3,7 +3,7 @@
## 效果引擎施加面归 BattleFxHost、奖励授予管线归 RewardGrant(两拆件,经组件出口 fx_host()/
## reward_grant() 取件,禁链式穿透其内部);本类保留宿主职责面=状态机与生命周期、轮 facts 缓冲、
## 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(),禁链式穿透内部协作对象。
## 伤害只向 DamageController 开单(何时开单归编排、怎么算归控制器);刷新唯一通道 player_request_refresh,禁 RAP 满自动刷。
class_name BattleOrchestrator
@@ -31,74 +31,60 @@ signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
# fx_reveal=专长揭示掷格(落空行无真实格);
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指)
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp"]
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指);
# fx_board_refresh=成功刷新事实族(迭代 46 卡A——崩壁冲击群伤行源=换图动作而非棋盘格)
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp", "fx_board_refresh"]
var _facts: BattleFacts # 战斗事实件(battle_facts.gd——事实族 15 成员单点)
var _rounds: RoundCycle # 轮循环驱动件(settlement/round_cycle.gd——五段持有/段序/重入闸/摇号)
var db: ConfigDb
var rng: RngServiceCore
# E1 封装收口:协作对象 board_generator/enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
# E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
# minesweeper/action_economy 保持公有 = 架构文档 API Boundaries 的 HUD框架消费面(意图提交 settle_batch + compute_move_path
# 只读;ap/rap/相位读取)——非本文档登记的契约面,勿增新用途。
var _board_generator: BoardGenerator
# 装板域(BoardGenerator+装板六函数族)已拆 BoardInstaller——生成器实例随件自建,宿主零成员。
var minesweeper: MinesweeperRules
var action_economy: ActionEconomy
var _enemy_system: UnitContainer
var _status_effects: StatusContainer
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 _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一)
var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二)
var encounter: Dictionary[String, Variant] = {}
var round_index := 0
var battle_result := "" # "" / "victory" / "defeat" / "fatal_error"
var _facts_reveals: Array[Dictionary] = []
var _facts_seals: Array[Dictionary] = []
var _facts_path := ""
var _settling := false
# 结算五段 Segment(段边界=审计五段,段序骨架冻结于
# SettlementTable.segment_order 语义:授予→增益/减益→战斗→衰减→判定。本类做段序编排+
# 行发射/开单原语单点——段体逻辑归 game/core/settlement/ 五类)
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 _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,无玩家发起入口)
# 同调装配运行态(begin_battle 经 enc 携带 attunement 键注入 facts;off=空集全部效果零扰动)
# 转存时点=roll_round_actions 轮末(轮内部分结算不更新 last);开战清零归 begin_battle
var nest_preview_count := -1 # 巢穴预告读数口(board_installed 命中落值;-1=未装配/未发射;呈现桥消费)
# 特性运行态(relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经
# 效果引擎分发;魔矢每战首次计数 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=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账)
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
# 消费面;null=直构零装配,充能面跳过)
func _attune_loadout() -> AttuneLoadout:
return encounter.get("loadout", null)
return _facts.encounter.get("loadout", null)
# 授予件战斗域回调束(bind_battle 注入——键集封闭三键:充能消耗/真视计数/负向踩雷计数;
# 宿主侧各转 _attune_loadout/_journal/_mines_hits 单点,授予件对账本零强引用)
# 宿主侧各转 _attune_loadout/_journal/_rounds.mines_hits 单点,授予件对账本零强引用)
func _battle_hooks() -> Dictionary[Variant, Variant]:
return {
"attune_consume": _hook_attune_consume,
@@ -108,8 +94,8 @@ func _battle_hooks() -> Dictionary[Variant, Variant]:
func _hook_attune_consume(item_id: String) -> void:
if _attune_loadout() != null and treasure_inventory != null:
_attune_loadout().consume_charge(item_id, treasure_inventory)
if _attune_loadout() != null and _reward_grant.treasure_inventory != null:
_attune_loadout().consume_charge(item_id, _reward_grant.treasure_inventory)
func _hook_vision_add() -> void:
@@ -120,8 +106,8 @@ func _hook_vision_add() -> void:
# 负向 blast(雷)踩雷计数入账(触发条件事实面——正向 spark_aoe/清图奖励不计入;
# 极性判定在授予件 _flat_blast 传点,本口只记账)
func _book_negative_blast() -> void:
_mines_hits["round"] += 1
_mines_hits["total"] += 1
_rounds.mines_hits["round"] += 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)
@@ -136,10 +122,10 @@ func _fx_host_fact(fact: String) -> Variant:
"round_index":
return round_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":
var hits_n := 0
for fevt in _facts_seals:
for fevt in _facts.seals:
if bool(fevt.get("hit", false)):
hits_n += 1
return hits_n
@@ -151,7 +137,7 @@ func _fx_host_fact(fact: String) -> Variant:
# attune 积点读投影(RewardGrant attune_safe_seen_cb 注入面——use_treasure 拒绝文案读数;经读投影出户防组件回调宿主私有族)
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:
return attunement.has(item_id)
return _facts.attunement.has(item_id)
# 装配集只读出口(_attune_has 契约化出口,save v6 只读访问器先例。只读零副作用——
@@ -179,8 +165,8 @@ func attune_has(item_id: String) -> bool:
# kind 分发查询先例=_attune_refresh_x_total bonus_refresh。零装配=0)
func attune_param_sum(kind: String) -> int:
var total := 0
for iid_v in attunement:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
for iid_v in _facts.attunement:
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(iid_v))
if (arow.get("effect_kinds", []) as Array).has(kind):
total += int(arow.get("magnitude", 0))
return total
@@ -189,8 +175,8 @@ func attune_param_sum(kind: String) -> int:
# 装配集内 ap_charge_safe 行读取(取数源=装配集行扫描;
# 首行语义=装配集内至多一件积点制〔试炼镐〕)
func _attune_charge_safe_row() -> Dictionary:
for iid_v in attunement:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(iid_v))
for iid_v in _facts.attunement:
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"):
return arow
return {}
@@ -200,9 +186,9 @@ func _attune_charge_safe_row() -> Dictionary:
# (used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner
# set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面)
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 # 直构零装配/库存未就位——充能回满跳过(空装配同语义)
_attune_loadout().reset_cycle(reset, treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体)
_attune_loadout().reset_cycle(reset, _reward_grant.treasure_inventory) # 写时种行/补行(按周期筛——语义逐字承原循环体)
# 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行:
@@ -213,30 +199,25 @@ func attune_convert_state() -> Dictionary:
if row.is_empty() or String(row.get("form", "")) != "active_charged":
return {"assembled": false, "points": 0, "need": 0, "display_name": "", "item_id": ""}
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", ""))}
# 特性求值(效果行为=数据行 triggers 订阅,效果引擎 dispatch 分发;
# 流派档位质变=FeatsEngine 纯函数求值
# (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 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:
db = db_ref
rng = rng_ref
_board_generator = BoardGenerator.new()
action_economy = ActionEconomy.new(db)
minesweeper = MinesweeperRules.new(db, action_economy)
_enemy_system = UnitContainer.new(db)
_status_effects = StatusContainer.new(db)
_facts = BattleFacts.new() # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4)
_reward_economy = RewardEconomy.new(db, rng)
_board_generator.fatal_handler = _fatal; _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_damage = DamageController.new(db, rng, _enemy_system, _status_effects)
_damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄)
_fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper, "action_economy": action_economy,
@@ -253,11 +234,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
minesweeper.round_end_board.connect(_on_round_end)
action_economy.round_end_stopped.connect(_on_round_end)
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕)
_grant_seg = GrantSegment.new(self, _fx_host, _reward_grant)
_aura_seg = AuraSegment.new(self)
_combat_seg = CombatSegment.new(self, _fx_host, _reward_grant)
_decay_seg = DecaySegment.new(self)
_verdict_seg = VerdictSegment.new(self)
# 结算五段接线随轮循环驱动件迁驻(round_cycle.gd——段件持有/段序/重入闸/摇号单点;构造序禁环不变)
_rounds = RoundCycle.new(self, _fx_host, _reward_grant)
# ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)----
@@ -268,9 +246,9 @@ func begin_battle(enc: Dictionary) -> bool:
return false
if not _begin_battle_accept(enc):
return false
if not _install_new_board():
if not _install_and_reset():
return false
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
_facts.reset_round() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
_begin_battle_fx_state(enc)
_begin_battle_attune_params(enc)
_begin_battle_preview_economy(enc)
@@ -282,14 +260,14 @@ func begin_battle(enc: Dictionary) -> bool:
if not action_economy.notify_round_start():
_fatal("行动经济 首轮 notify_round_start 被拒")
return false
_roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
_rounds.roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
round_started.emit(round_index)
return true
# 受理段:encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。
# 受理段:_facts.encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。
func _begin_battle_accept(enc: Dictionary) -> bool:
encounter.assign(enc)
_facts.encounter.assign(enc)
_reward_grant.reset_battle() # 战斗域零 GP 持久(每场战斗缓冲从零起算;案C 闸复位)
_damage.set_hero_ac_override(int(enc.get("hero_armor_class", -1))) # 主角护甲合流入参(伤害控制器 defender_ac 消费)
db.set_reload_gate(true)
@@ -300,11 +278,9 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
if dice.is_empty():
_fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))])
return false
# E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径)
_hero_atk = int(enc.get("hero_atk", int(db.hero.atk)))
if not _enemy_system.spawn(int(enc.level_index), int(enc.node_depth), int(enc.hero_hp), dice[0],
_hero_atk, int(enc.get("hero_max_hp", -1)), dice[1],
String(enc.get("attack_type", "")), String(enc.get("armor_type", "")), String(enc.get("node_type", "battle"))): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss)
# E4 收口:加成经 _facts.encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径;
# _hero_atk 派生读=受理后不变量,无快照成员)
if not _enemy_system.spawn({"level_index": int(enc.level_index), "node_depth": int(enc.node_depth), "hero_hp": int(enc.hero_hp), "unit_count_overrides": dice[0], "hero_atk_override": _hero_atk(), "hero_max_hp_override": int(enc.get("hero_max_hp", -1)), "enemy_atk_overrides": dice[1], "hero_attack_type": String(enc.get("attack_type", "")), "hero_armor_type": String(enc.get("armor_type", "")), "row_kind": String(enc.get("node_type", "battle"))}): # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss)
_fatal("敌人系统 spawn 被拒")
return false
return true
@@ -312,12 +288,12 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
# 遭遇骰(难度曲线与Boss GDD Core Rule 8):行有 unit_count_max 时按 [unit_count, max] 均匀骰;
# 行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值
# encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径);
# _facts.encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径);
# 敌人系统零随机纪律保持(收 override 即确定值)。
# 返回 [count_overrides, atk_overrides];行集为空 = 返回 [](fail 判据交受理段 _fatal)。
func _begin_battle_dice(enc: Dictionary) -> Array:
# 行类维度:节点身份 battle/boss 决定行集键域(缺省 battle 只服务测试直呼面——生产路径恒传 node_type)
var erowset_pre: Array[Variant] = db.resolve_enemy_rowset(int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle")))
var erowset_pre: Array[Variant] = db.combat.enemy.resolve_rowset(int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle")))
if erowset_pre.is_empty():
return []
var needs_count := false
@@ -355,12 +331,12 @@ func _begin_battle_dice_roll(rows: Array, estream: RandomNumberGenerator, roll_a
# 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零)
# 扩签运行态就位(归 fx_host)+账本挂接+桶挂引用+揭示/AC 两回调+rider 档位恒零+订阅重建。
func _begin_battle_fx_state(enc: Dictionary) -> void:
# 同调装配运行态注入(enc.attunement 携带;off=空集零扰动。淬毒 N 注入
# 同调装配运行态注入(enc._facts.attunement 携带;off=空集零扰动。淬毒 N 注入
# 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install)
attunement = (enc.get("attunement", []) as Array).duplicate()
_attune_safe_seen = 0
_facts.attunement = (enc.get("attunement", []) as Array).duplicate()
_facts.attune_safe_seen = 0
_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)
_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()) # 授予件战斗域回调束(充能消耗/真视计数/踩雷计数)
@@ -371,13 +347,13 @@ func _begin_battle_fx_state(enc: Dictionary) -> void:
# 不设档位机制」):rider_tier 两注入恒零,散件单体效果=唯一形态
_damage.inject["rider_tier_bonus"] = 0
_damage.inject["rider_tier_crit"] = false
_feat_first_dig_used = false
_facts.feat_first_dig_used = false
# 树 rule 键消费(Fork 激活侧效果=效果引擎合成
# 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1)。其余 rule 键
# =stat 面投影不入战斗(T8 表内容 provisional 口径披露))
var tree_rules: Array[Variant] = enc.get("tree_rules", []) as Array
# 效果引擎订阅重建(装载序=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 分发:远程镐挖距放宽/信标网络封印可达豁免;
@@ -413,144 +389,27 @@ func _begin_battle_preview_economy(enc: Dictionary) -> void:
action_economy.grant_reserve_ap(voucher_rap)
func _install_new_board(during_round := false) -> bool:
var stream: RandomNumberGenerator = rng.open_stream("board_gen")
var board: BoardData = _board_generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db)
if board == null:
# ---- 装板受理薄壳(BoardInstaller 拆件后的宿主承接面——begin_battle/中途刷新/轮内刷新三调用点唯一入口)----
# 件内装板(生成→全局变异决议→装板受理→资源摇号→变异布置)成功后执行宿主态复位:五处计数清零+
# 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。
func _install_and_reset(during_round := false) -> bool:
var installer := BoardInstaller.new({"db": db, "rng": rng, "minesweeper": minesweeper,
"reward_economy": _reward_economy, "encounter": _facts.encounter, "fatal_cb": _fatal})
if not installer.install(during_round):
return false
# 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant)
_resolve_global_variant(board)
var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round)
if not res.accepted:
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
return false
# 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池)
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var eligible: Array[Dictionary] = []
for c in board.safe_cells():
if not r0.has(c):
eligible.append({"cell": c})
for c in board.mines.keys():
if not r0.has(c):
eligible.append({"cell": c})
if not _reward_economy.roll_board(eligible):
_fatal("地图战斗资源 roll_board 被拒")
return false
# 地形变异 布置段(棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 →
# 战斗资源格设置 → 变异格设置)——布置在初始展开与资源格之后,变异天然不落已开区
_place_variants(board)
_seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_seal_miss_seen.clear()
_perfect_hit_true = 0 # 整图完美计数随 install 重置
_perfect_hit_false = 0
_perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零)
_board_from_midround_refresh = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false
_board_seq += 1
_facts.seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_facts.seal_miss_seen.clear()
_facts.perfect_hit_true = 0 # 整图完美计数随 install 重置
_facts.perfect_hit_false = 0
_facts.perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零)
_facts.board_from_midround = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false
_facts.board_seq += 1
board_changed.emit()
if treasure_inventory != null:
treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还)
if _reward_grant.treasure_inventory != null:
_reward_grant.treasure_inventory.retire_overlays() # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还)
return true
# ---- 全局作用域变异 决议(装板序内 roll_board 之前)----
# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在
# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。
# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走
# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。
func _resolve_global_variant(board: BoardData) -> void:
board.board_variants.clear()
_reward_economy.board_skew = 0
var row: Dictionary[Variant, Variant] = db.global_variant_row(int(encounter.level_index), String(board.board_type),
String(encounter.get("node_type", "")))
if row.is_empty():
return
board.board_variants = [String(row.variant_id)]
_reward_economy.board_skew = int(row.get("effect_value", 0))
# ---- 地形变异 布置段(棋盘设置顺序第 4 步)----
# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool;
# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。
# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后,
# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。
# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) +
# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例,
# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。
# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。
func _place_variants(board: BoardData) -> void:
if not board.board_variants.is_empty():
return
var pool: Array[Variant] = db.variant_pool(int(encounter.level_index), String(board.board_type))
if pool.is_empty():
return
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var candidates: Array[Vector2i] = []
for c in board.safe_cells():
if not r0.has(c):
candidates.append(c)
if candidates.is_empty():
return
var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次)
var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)]
var total: int = int(round(float(candidates.size()) * float(db.variation.percent_of_cells) / 100.0)) \
+ (int(encounter.level_index) - 1) * int(db.variation.per_level_bonus)
total = mini(total, candidates.size())
_place_variants_apply(board, candidates, stream, vrow, total)
# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费)
func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void:
var placed := {}
while placed.size() < total:
var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.variation.pack_max)))
var free: Array[Vector2i] = []
for c in candidates:
# 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量
# (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言)
if placed.has(c) or _adjacent_to_packs(board, placed, c):
continue
free.append(c)
if free.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)]
placed[seed_c] = true
var pack: Array[Variant] = [seed_c]
var pack_set := {seed_c: true}
while pack.size() < pack_size:
var fringe := _variants_fringe(board, pack, candidates, placed, pack_set)
if fringe.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)]
placed[nxt] = true
pack_set[nxt] = true
pack.append(nxt)
for c in placed.keys():
board.variants[c] = [String(vrow.variant_id)]
# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接)
func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]:
var fringe: Array[Vector2i] = []
for c in pack:
for n in board.neighbors8(c):
if candidates.has(n) and not placed.has(n) and not fringe.has(n) \
and not _adjacent_to_packs(board, placed, n, pack_set):
fringe.append(n)
return fringe
# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max
func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool:
for m in board.neighbors8(c):
if placed.has(m) and not own_pack.has(m):
return true
return false
# ---- 事件缓冲(轮 facts 收集;结算触发 = Waiting 相位编排点,不依赖到达序)----
func _on_cell_revealed(evt: Dictionary) -> void:
@@ -568,9 +427,9 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if String(evt.get("kind", "")) == "safe":
var cs_row: Dictionary[Variant, Variant] = _attune_charge_safe_row()
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))
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)))
if bool(evt.get("resource", false)):
# 身份快照随缓冲捕获(批次隔离——轮内刷新后 mapping 已换批,授予必须用缓冲期身份,
@@ -579,47 +438,20 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if snap != null:
evt = evt.duplicate()
evt["reward_snapshot"] = snap
_facts_reveals.append(evt)
_facts.reveals.append(evt)
func _on_seal_judged(evt: Dictionary) -> void:
_facts_seals.append(evt)
_facts.seals.append(evt)
func _on_round_end(_payload: Dictionary) -> void:
_facts_path = String(_payload.get("path_tag", ""))
_maybe_settle()
_facts.path = String(_payload.get("path_tag", ""))
_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:
if positive and pos_m <= 0:
return snap
@@ -638,7 +470,7 @@ func _harmful_variants(vids: Array) -> Array:
var out: Array[Variant] = []
for vid_v in vids:
var vid := String(vid_v)
var vrow: Dictionary[Variant, Variant] = db.variation.row_by_id(vid)
var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(vid)
if String(vrow.get("effect", "")) == "flat_damage":
out.append(vid)
return out
@@ -676,64 +508,14 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
# 摇定后、兑现前死亡 → 本拍技能作废。
func _roll_round_actions() -> void:
var pity_m: int = int(db.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 as Dictionary).is_empty() else String((action_economy.last_round_end as Dictionary).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.skill_row(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:
return _damage.defender_ac(unit)
func _pool_effective(effect_id: String) -> int:
var side := "hero"
var row: Dictionary[Variant, Variant] = db.status_effect_row(effect_id)
var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(effect_id)
if not row.is_empty():
side = String(row.target_side)
for eff in _status_effects.query_active(side):
@@ -763,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})
var mines_hit := 0
var safe_n := 0
for evt in _facts_reveals:
for evt in _facts.reveals:
if evt.kind == "mine":
mines_hit += 1
elif evt.kind == "safe":
safe_n += 1
var board_stats := {"seq": _board_seq}
var board_stats := {"seq": _facts.board_seq}
if minesweeper.current_board() != null:
board_stats["w"] = minesweeper.current_board().width
board_stats["h"] = minesweeper.current_board().height
@@ -776,18 +558,18 @@ func _emit_settlement_snapshot(verdict: String) -> void:
board_stats["seals"] = minesweeper.seal_count()
settlement_snapshot.emit({
"kind": "settlement",
"level": int(encounter.get("level_index", 0)),
"depth": int(encounter.get("node_depth", 0)),
"level": int(_facts.encounter.get("level_index", 0)),
"depth": int(_facts.encounter.get("node_depth", 0)),
"round": round_index,
"verdict": verdict,
"unit_count": enemies.size(),
"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())},
"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,
"enemies": enemies,
"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(), },
})
@@ -795,12 +577,12 @@ func _emit_settlement_snapshot(verdict: String) -> void:
func _exit_distribution() -> void:
var mode := "same_board"
if _facts_path == "cleared":
if _facts.path == "cleared":
mode = "new_board" # cleared 轮恒走 new_board(契约义务);非清图轮恒同图(等待期刷新通道已移除)
if mode == "new_board":
_reward_economy.retire_mapping()
minesweeper.retire_board()
if not _install_new_board():
if not _install_and_reset():
_fatal("换图编排失败")
return
# 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注)
@@ -809,32 +591,23 @@ func _exit_distribution() -> void:
round_index += 1
_fx_host.emit_round_start()
action_economy.notify_round_start()
_reset_round_facts()
_refresh_used_this_round = 0
_roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
_facts.reset_round()
_facts.refresh_used = 0
_rounds.roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
round_started.emit(round_index)
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
func can_refresh() -> bool:
return battle_result == "" \
and _refresh_used_this_round < int(db.action_economy.refresh_cap_per_round) \
and action_economy.phase == action_economy.PHASE_ACTIVE \
and action_economy.rap >= int(db.action_economy.rap_refresh_cost)
return BattleRefreshFlow.can_refresh(self)
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
# 挖掘期唯一通道:Active 相位立即换图
# 继续挖(RAP -8;AP 保留、相位不动、不触发结算);非挖掘期请求一律拒绝——非清图轮恒同图。
# 挖掘期唯一通道:Active 相位立即换图继续挖(RAP 按折后价扣;AP 保留、相位不动、不触发结算);
# 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
func player_request_refresh() -> bool:
if not can_refresh():
return false
if not action_economy.request_refresh():
return false
_refresh_used_this_round += 1
_reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
minesweeper.retire_board(true) # 旧板退役(轮内通道)
return _install_new_board(true) # 当场换图——失败即编排违例 fail-fast
return BattleRefreshFlow.execute(self)
func _battle_over(result: String) -> void:
@@ -857,11 +630,11 @@ func _battle_over(result: String) -> void:
_reward_economy.retire_mapping()
_enemy_system.despawn_all()
_status_effects.notify_battle_end()
if treasure_inventory != null:
treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗)
if _reward_grant.treasure_inventory != null:
_reward_grant.treasure_inventory.retire_overlays() # 叠加层随战斗结束清空(库存不动——关卡域跨战斗)
action_economy.notify_combat_end() # → Idle,RAP 清零
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})
@@ -869,19 +642,19 @@ func _battle_over(result: String) -> void:
# gp_dropped = gp_drop 封闭流掷定(tier 行的 roll_min..roll_max 均匀整数——每战斗末恰一 scope)
# + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。
func _roll_gp_drop() -> int:
var tier := "boss" if String(encounter.get("node_type", "")) == "boss" else "normal"
var drops: Dictionary[Variant, Variant] = db.shop_gp_drop_row(tier)
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 roll := 0
if drops.is_empty():
push_error("[战斗结算] gp_drops 缺行 tier=%s——掉落掷定按 0 兑付" % tier)
else:
var stream: RandomNumberGenerator = rng.open_stream("gp_drop")
roll = stream.randi_range(int(drops.get("roll_min", 0)), int(drops.get("roll_max", 0)))
var drop := roll + db.shop_level_gp_bonus(int(encounter.get("level_index", 0)))
var drop := roll + db.run.shop.level_gp_bonus_at(int(_facts.encounter.get("level_index", 0)))
# 财富之道·聚(战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕,
# 店内 escrow 不乘〔范围=仅战斗掉落渠道〕;本函数返回值=结算框「GP 增量」行
# 显示源,落在入账前=显示即实得;乘数 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))
return drop + _reward_grant.escrow
@@ -899,14 +672,14 @@ func presentation_snapshot() -> Dictionary:
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行)
var enemy_effects: Array[Dictionary] = []
for eff in _status_effects.query_active("enemy"):
var erow: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
var erow: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff.effect_id)
if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]:
enemy_effects.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)})
for i in all.size():
var u: Dictionary[Variant, Variant] = all[i]
var alive: bool = bool(u.get("alive", false))
var pending: String = String(u.get("pending_skill", "")) if alive else ""
var pending_row: Dictionary[Variant, Variant] = db.skill_row(pending)
var pending_row: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(pending)
enemies.append({
"order": i + 1,
"display_name": String(u.display_name),
@@ -932,7 +705,7 @@ func presentation_snapshot() -> Dictionary:
"phase": String(action_economy.phase),
"ap": int(action_economy.ap), "rap": int(action_economy.rap),
"can_refresh": can_refresh(),
"refresh_used": _refresh_used_this_round, # 受理计数入呈现成文面(HUD 余量单源读此处)
"refresh_used": _facts.refresh_used, # 受理计数入呈现成文面(HUD 余量单源读此处)
"hero": hero,
"enemies": enemies,
"hero_effects": _status_effects.query_active("hero").duplicate(true), # 值拷贝出户(快照消费方直改不渗入状态容器)
@@ -959,12 +732,12 @@ func diagnostic_snapshot() -> Dictionary:
return {
"battle_result": battle_result,
"round": round_index,
"settling": _settling,
"refresh_used_round": _refresh_used_this_round,
"board_seq": _board_seq,
"facts_path": _facts_path,
"facts_reveals": _facts_reveals.size(),
"facts_seals": _facts_seals.size(),
"settling": _rounds.is_settling(),
"refresh_used_round": _facts.refresh_used,
"board_seq": _facts.board_seq,
"facts_path": _facts.path,
"facts_reveals": _facts.reveals.size(),
"facts_seals": _facts.seals.size(),
"action_economy": {"phase": String(action_economy.phase), "ap": action_economy.ap, "rap": action_economy.rap},
"minesweeper_board_installed": minesweeper.current_board() != null,
"minesweeper_revealed": minesweeper.revealed_cells().size() if minesweeper.current_board() != null else 0,
@@ -975,12 +748,6 @@ func diagnostic_snapshot() -> Dictionary:
}
func _reset_round_facts() -> void:
_facts_reveals.clear()
_facts_seals.clear()
_facts_path = ""
# ---- 行协议(Core Rule:公共字段 + 行类型特有字段)----
func _emit_row(head: Dictionary, ri: int, display: String, value: int, extra: Dictionary) -> void:
@@ -1031,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:
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
+128 -83
View File
@@ -22,12 +22,19 @@ static func target_mine_count(params: Dictionary) -> int:
# rng_stream = 一次 open_stream("board_gen") 实例(一张图的全部取数;重试共用——attempt 边界非 scope 事件)。
# db 参数不标具体类型原因:冒烟工具注入 FakeDB 桩(experiment_density.gd 等)——typed 形参会拒收桩表
func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardData: # type-hint: ignore(测量面 _BoardRowStub 冒烟夹具鸭子型注入——typed 参拒 stub)
var row: Dictionary[Variant, Variant] = db.resolve_board_gen(L, D)
var row: Dictionary[Variant, Variant] = db.combat.board_gen.resolve_row(L, D)
if row.is_empty():
_fatal("BoardGen 行缺失 (%d,%d)" % [L, D])
return null
# 生成期表值单次读取快照(防热重载撕裂 attempt 语义)
var params := {
var params := _board_params(row, L, D)
var outcome := _attempt_loop(params, rng_stream, db)
_finalize(outcome["board"], params, outcome["attempts"], outcome["mode"], outcome["r0_rej"])
return outcome["board"]
## 生成期表值单次读取快照(防热重载撕裂 attempt 语义;零 RNG)
func _board_params(row: Dictionary, L: int, D: int) -> Dictionary:
return {
"L": L, "D": D,
"w": int(row.board_width), "h": int(row.board_height),
"density": float(row.mine_density),
@@ -41,10 +48,11 @@ func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardDat
"shave_r": int(row.get("shave_ring_radius", 1)),
"r0_cap": int(row.get("r0_max_cap", 0)),
}
var best_legal: BoardData = null
var best_legal_score := -1.0
var best_bad: BoardData = null
var best_badness := [999999, 999999, 999999]
## 重试主循环:逐尝试评分择优,产出三分出口结果(board/mode/attempts/r0_rej)
func _attempt_loop(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Dictionary: # type-hint: ignore(同 generate——测量面 stub 注入)
var best := {"legal": null, "legal_score": -1.0, "bad": null, "badness": [999999, 999999, 999999]}
var attempts_used := 0
var accepted: BoardData = null
var accept_mode := ""
@@ -53,35 +61,37 @@ func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardDat
attempts_used = k
var b := _attempt(params, rng_stream, db)
var badness := _badness(b, params)
if best_bad == null or _lex_less(badness, best_badness):
best_bad = b
best_badness = badness
if best["bad"] == null or _lex_less(badness, best["badness"]):
best["bad"] = b
best["badness"] = badness
# R₀≤cap 重掷闸触发计数(迭代14① T3——尝试事实,与合法性判定独立记录;占比=T5 闸触发率读数)
if int(params.r0_cap) > 0 and b.compute_r0().size() > int(params.r0_cap):
r0_cap_rejected += 1
if legality(b, params):
var m: Dictionary[Variant, Variant] = measure_inferability(b) # 求解器每合法板恰一次(O3 收口——此前重复调用两次)
var inf: float = m["inferability"]
if best_legal == null or inf > best_legal_score:
best_legal = b
best_legal_score = inf
var inf: float = measure_inferability(b)["inferability"] # 求解器每合法板恰一次(O3 收口——此前重复调用两次)
if best["legal"] == null or inf > best["legal_score"]:
best["legal"] = b
best["legal_score"] = inf
if inf >= params.target: # 单一判据(棋盘生成:现行规则已回归"可推理率 ≥ 及格线")
accepted = b
accept_mode = "first_pass"
break
if accepted == null:
if best_legal != null:
accepted = best_legal
accept_mode = "accept_best"
accepted.degraded_product = true # 降级标记(独立于 illegal_product——三分出口表要求标记随产物上抛)
push_warning("[棋盘生成] 重试耗尽无达标——接受最优出图(强告警埋点,target=%.2f best=%.3f)" % [params.target, best_legal_score])
else:
accepted = best_bad
accept_mode = "accept_worst"
accepted.illegal_product = true
push_error("[棋盘生成] attempts 耗尽零合法——接受最不坏出图 + 非法产物标记(玩家永不卡死)")
_finalize(accepted, params, attempts_used, accept_mode, db, r0_cap_rejected)
return accepted
var fallback := _accept_fallback(params, best["legal"], best["legal_score"], best["bad"])
accepted = fallback[0]
accept_mode = fallback[1]
return {"board": accepted, "mode": accept_mode, "attempts": attempts_used, "r0_rej": r0_cap_rejected}
## 三分出口(重试耗尽):接受最优(降级标记)或接受最不坏(非法产物标记——玩家永不卡死)
func _accept_fallback(params: Dictionary, best_legal: BoardData, best_legal_score: float, best_bad: BoardData) -> Array:
if best_legal != null:
best_legal.degraded_product = true # 降级标记(独立于 illegal_product——三分出口表要求标记随产物上抛)
push_warning("[棋盘生成] 重试耗尽无达标——接受最优出图(强告警埋点,target=%.2f best=%.3f)" % [params.target, best_legal_score])
return [best_legal, "accept_best"]
best_bad.illegal_product = true
push_error("[棋盘生成] attempts 耗尽零合法——接受最不坏出图 + 非法产物标记(玩家永不卡死)")
return [best_bad, "accept_worst"]
# ==================== 五阶段:单次尝试 ====================
@@ -94,8 +104,20 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar
b.board_type = str(params.board_type)
b.level_index = params.L
b.node_depth = params.D
# ② 布雷:N = round(d×(W×H−9)) 先定数;域 = 全格 − 出生 3×3;均匀布雷后"先布后删"
# (−9 = 出生区格数,常量单点 BoardData.SPAWN_AREA_CELLS)
# ② 布雷:N = round(d×(W×H−9)) 先定数;域 = 全格 − 出生 3×3;均匀布雷后"先布后删"(_populate_mines 承载——Fisher-Yates 取号段整体随迁)
var removed := _populate_mines(b, params, rng_stream)
# ③ 变异布置(地形变异 框架面):布置约束与算法随首批变异类型定稿(生成期布置接口结构位),
# 定稿前零布置——清单为空是合法态(P0),静默产出无变异图。
b.metrics = {
"metric_version": METRIC_VERSION,
"ring_removed": removed,
}
return b
## 布雷与剃雷域:域 = 全格 − 出生 3×3;均匀布雷(部分 Fisher-Yates,确定性,消费 board_gen 流)
## 后剃出生环雷,返回 ring_removed(指标语义不变——现值变小=收窄批预期)
func _populate_mines(b: BoardData, params: Dictionary, rng_stream: RandomNumberGenerator) -> int:
var spawn := {}
for c in b.spawn_cells():
spawn[c] = true
@@ -104,7 +126,7 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar
if not spawn.has(c):
domain.append(c)
var n_mines: int = target_mine_count(params)
# 部分 Fisher-Yates(确定性,消费 board_gen 流)
# 部分 Fisher-Yates(确定性,消费 board_gen 流)——取号序冻结(契约:同种子逐位一致)
for i in range(0, mini(n_mines, domain.size())):
var j := rng_stream.randi_range(i, domain.size() - 1)
var tmp = domain[i]
@@ -116,9 +138,14 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar
# 先布后删:剃雷域 = 距 spawn_center 切比雪夫距离 ≤1+r 的格(迭代14①收窄批 T2——原「R₀ flood 域∪
# 外扩 1 环」全域剃雷=开局开阔地巨大根因〔10×10 板 R₀ 均值 68.6=板面 68.6%〕;收窄后开阔地=
# 出生邻域外的自然 flood。r 读表 shave_ring_radius(r=1 → 5×5=25 格;r=0 即只剃出生 3×3)。
# 只减不增、无迭代语义不变;ring_removed 指标语义不变(现值变小=预期);零 RNG 消费变化——
# 同种子布雷序列逐位一致,仅移除域收窄。
var shave_r: int = int(params.get("shave_r", 1))
# 只减不增、无迭代语义不变;零 RNG 消费变化。
var removed := _shave_ring(b, int(params.get("shave_r", 1)))
_compute_digits(b)
return removed
## 剃雷环域(零 RNG):距出生心切比雪夫 ≤1+r 的环内剃雷,返回剃除数
func _shave_ring(b: BoardData, shave_r: int) -> int:
var ring := {}
for c in b.all_cells():
if maxi(absi(c.x - b.spawn_center.x), absi(c.y - b.spawn_center.y)) <= 1 + shave_r:
@@ -128,14 +155,7 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar
if b.mines.has(c):
b.mines.erase(c)
removed += 1
_compute_digits(b)
# ③ 变异布置(地形变异 框架面):布置约束与算法随首批变异类型定稿(生成期布置接口结构位),
# 定稿前零布置——清单为空是合法态(P0),静默产出无变异图。
b.metrics = {
"metric_version": METRIC_VERSION,
"ring_removed": removed,
}
return b
return removed
func _compute_digits(b: BoardData) -> void:
@@ -164,49 +184,12 @@ func measure_inferability(b: BoardData) -> Dictionary:
while changed:
changed = false
# 算子① 连锁:0 格的 8 邻安全格全入 R
var frontier: Array[Variant] = r.keys()
for x in frontier:
if safe_set.has(x) and b.digit_of(x) == 0:
for n in b.neighbors8(x):
if safe_set.has(n) and not r.has(n):
r[n] = true
changed = true
changed = _apply_chain_rule(b, r, safe_set) or changed
# 信息源:数字约束 single + subset(solver_rules=single_subset 冻结域)
var constraints := _build_constraints(b, r, known_mines, safe_set)
# single 规则
for con in constraints:
if con.mines == 0:
for c in con.unknown:
if not r.has(c):
r[c] = true
changed = true
elif con.mines == con.unknown.size():
for c in con.unknown:
if not known_mines.has(c):
known_mines[c] = true
changed = true
# subset 规则:U1 ⊂ U2 ⇒ (d2−d1, U2∖U1)
var derived := []
for i in constraints.size():
for j in range(i + 1, constraints.size()):
var a: Dictionary[Variant, Variant] = constraints[i]
var bb: Dictionary[Variant, Variant] = constraints[j]
var sub := _subset_of(a.unknown, bb.unknown)
if sub == 1:
derived.append(_diff_constraint(a, bb))
elif sub == 2:
derived.append(_diff_constraint(bb, a))
for con in derived:
if con.mines == 0:
for c in con.unknown:
if not r.has(c): # 同 no-peek 口径:推导结论不再用 safe_set 复核(约束本身可靠)
r[c] = true
changed = true
elif con.mines == con.unknown.size():
for c in con.unknown:
if not known_mines.has(c):
known_mines[c] = true
changed = true
changed = _apply_single_rules(constraints, r, known_mines) or changed
var derived := _derived_constraints(constraints)
changed = _apply_derived_rules(derived, r, known_mines) or changed
var r_inf := r.keys()
var s_count := safe_set.size()
var inferability: float = float(r_inf.size()) / float(maxi(1, s_count))
@@ -217,6 +200,68 @@ func measure_inferability(b: BoardData) -> Dictionary:
}
## 算子① 连锁域:0 格的 8 邻安全格全入 R(返回本轮是否有新增)
func _apply_chain_rule(b: BoardData, r: Dictionary, safe_set: Dictionary) -> bool:
var changed := false
var frontier: Array[Variant] = r.keys()
for x in frontier:
if safe_set.has(x) and b.digit_of(x) == 0:
for n in b.neighbors8(x):
if safe_set.has(n) and not r.has(n):
r[n] = true
changed = true
return changed
## single 规则域:零雷约束开格/满雷约束标雷(返回本轮是否有新增)
func _apply_single_rules(constraints: Array, r: Dictionary, known_mines: Dictionary) -> bool:
var changed := false
for con in constraints:
if con.mines == 0:
for c in con.unknown:
if not r.has(c):
r[c] = true
changed = true
elif con.mines == con.unknown.size():
for c in con.unknown:
if not known_mines.has(c):
known_mines[c] = true
changed = true
return changed
## subset 派生域:U1 ⊂ U2 ⇒ (d2−d1, U2∖U1)(对约束对做包含判定,产出派生约束集)
func _derived_constraints(constraints: Array) -> Array:
var derived := []
for i in constraints.size():
for j in range(i + 1, constraints.size()):
var a: Dictionary[Variant, Variant] = constraints[i]
var bb: Dictionary[Variant, Variant] = constraints[j]
var sub := _subset_of(a.unknown, bb.unknown)
if sub == 1:
derived.append(_diff_constraint(a, bb))
elif sub == 2:
derived.append(_diff_constraint(bb, a))
return derived
## 派生规则域:派生约束的零雷开格/满雷标雷(同 no-peek 口径:推导结论不再用 safe_set 复核——约束本身可靠)
func _apply_derived_rules(derived: Array, r: Dictionary, known_mines: Dictionary) -> bool:
var changed := false
for con in derived:
if con.mines == 0:
for c in con.unknown:
if not r.has(c):
r[c] = true
changed = true
elif con.mines == con.unknown.size():
for c in con.unknown:
if not known_mines.has(c):
known_mines[c] = true
changed = true
return changed
func _build_constraints(b: BoardData, r: Dictionary, known_mines: Dictionary, safe_set: Dictionary) -> Array:
var out: Array[Dictionary] = []
for c in r.keys():
@@ -337,7 +382,7 @@ func _neighbor_mine_count(b: BoardData, c: Vector2i) -> int:
# ==================== 产出上报(生成期指标集) ====================
func _finalize(b: BoardData, params: Dictionary, attempts_used: int, accept_mode: String, _db, r0_cap_rejected: int = 0) -> void: # type-hint: ignore(同 generate——测量面 stub 注入)
func _finalize(b: BoardData, params: Dictionary, attempts_used: int, accept_mode: String, r0_cap_rejected: int = 0) -> void:
var m := measure_inferability(b)
# 产物断言(棋盘生成 出生区安全与反退化布雷):实际雷数 ≤ 目标雷数 N——先布后删只减不增,失败即致命
var n_target: int = target_mine_count(params)
+167
View File
@@ -0,0 +1,167 @@
## 棋盘安装与变异布置域件(B1 域·BoardInstaller):生成受理、全局作用域变异决议、装板受理、
## 资源摇号资格域组装(roll_board 恰一次——install 后、挖掘前)、region 变异决议与连通片布置。
## 棋盘设置顺序 = 生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置(布置在初始展开与
## 资源格之后,变异天然不落已开区)。宿主侧装板后状态复位(封印/完美计数清零、板序推进、
## board_changed 发射、秘宝叠加层退役)留宿主薄壳——本件零复位职责。零 UI 零 await;
## 随机只经注入 RngServiceCore 的 board_gen/variant_place 两封闭流(流名与开流时点不变,
## 生成器只消费传入流——同种子同输入逐位复现)。
class_name BoardInstaller
extends RefCounted
var db: ConfigDb
var rng: RngServiceCore
var minesweeper: MinesweeperRules
var reward_economy: RewardEconomy
var encounter: Dictionary[String, Variant] # 宿主 encounter 同对象引用(assign 原地变异——跨战活引用)
var fatal_cb: Callable # 协作件违约→宿主 _fatal 封印(单点接线——BoardGenerator 同款)
var generator: BoardGenerator # 件内自建自接线(原宿主 _board_generator 成员随件迁移)
func _init(bundle: Dictionary) -> void:
for k in ["db", "rng", "minesweeper", "reward_economy", "encounter", "fatal_cb"]:
assert(bundle.has(k), "[装板域件] 引用束缺键: %s" % k)
db = bundle.db
rng = bundle.rng
minesweeper = bundle.minesweeper
reward_economy = bundle.reward_economy
encounter = bundle.encounter
fatal_cb = bundle.fatal_cb
generator = BoardGenerator.new()
generator.fatal_handler = fatal_cb # 协作件违约→宿主 _fatal 封印(单点接线)
func _fatal(msg: String) -> void:
if fatal_cb.is_valid():
fatal_cb.call(msg) # 组装层注入 orchestrator._fatal(封印传导,不绕第二中止通道)
else:
push_error(msg)
assert(false, msg)
# 装板单入口(生成 → 全局变异决议 → 装板受理 → 资源摇号 → 变异布置);
# 宿主态复位归宿主薄壳(_install_and_reset)——本件返回即装板完成、复位未行。
func install(during_round := false) -> bool:
var stream: RandomNumberGenerator = rng.open_stream("board_gen")
var board: BoardData = generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db)
if board == null:
return false
# 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant)
_resolve_global_variant(board)
var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round)
if not res.accepted:
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
return false
# 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池)
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var eligible: Array[Dictionary] = []
for c in board.safe_cells():
if not r0.has(c):
eligible.append({"cell": c})
for c in board.mines.keys():
if not r0.has(c):
eligible.append({"cell": c})
if not reward_economy.roll_board(eligible):
_fatal("地图战斗资源 roll_board 被拒")
return false
# 地形变异 布置段(棋盘设置顺序第 4 步)——布置在初始展开与资源格之后,变异天然不落已开区
_place_variants(board)
return true
# ---- 全局作用域变异 决议(装板序内 roll_board 之前)----
# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在
# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。
# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走
# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。
func _resolve_global_variant(board: BoardData) -> void:
board.board_variants.clear()
reward_economy.board_skew = 0
var row: Dictionary[Variant, Variant] = db.combat.variation.global_variant_row(int(encounter.level_index), String(board.board_type),
String(encounter.get("node_type", "")))
if row.is_empty():
return
board.board_variants = [String(row.variant_id)]
reward_economy.board_skew = int(row.get("effect_value", 0))
# ---- 地形变异 布置段(棋盘设置顺序第 4 步)----
# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool;
# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。
# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后,
# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。
# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) +
# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例,
# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。
# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。
func _place_variants(board: BoardData) -> void:
if not board.board_variants.is_empty():
return
var pool: Array[Variant] = db.combat.variation.variant_pool(int(encounter.level_index), String(board.board_type))
if pool.is_empty():
return
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var candidates: Array[Vector2i] = []
for c in board.safe_cells():
if not r0.has(c):
candidates.append(c)
if candidates.is_empty():
return
var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次)
var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)]
var total: int = int(round(float(candidates.size()) * float(db.combat.variation.percent_of_cells) / 100.0)) \
+ (int(encounter.level_index) - 1) * int(db.combat.variation.per_level_bonus)
total = mini(total, candidates.size())
_place_variants_apply(board, candidates, stream, vrow, total)
# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费)
func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void:
var placed := {}
while placed.size() < total:
var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max)))
var free: Array[Vector2i] = []
for c in candidates:
# 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量
# (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言)
if placed.has(c) or _adjacent_to_packs(board, placed, c):
continue
free.append(c)
if free.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)]
placed[seed_c] = true
var pack: Array[Variant] = [seed_c]
var pack_set := {seed_c: true}
while pack.size() < pack_size:
var fringe := _variants_fringe(board, pack, candidates, placed, pack_set)
if fringe.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)]
placed[nxt] = true
pack_set[nxt] = true
pack.append(nxt)
for c in placed.keys():
board.variants[c] = [String(vrow.variant_id)]
# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接)
func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]:
var fringe: Array[Vector2i] = []
for c in pack:
for n in board.neighbors8(c):
if candidates.has(n) and not placed.has(n) and not fringe.has(n) \
and not _adjacent_to_packs(board, placed, n, pack_set):
fringe.append(n)
return fringe
# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max
func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool:
for m in board.neighbors8(c):
if placed.has(m) and not own_pack.has(m):
return true
return false
+1
View File
@@ -0,0 +1 @@
uid://di6u170vqf8ae
+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
+49 -320
View File
@@ -1,12 +1,14 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与类型化访问器(垂直切片验证:改表即改玩法,数值零硬编码)。
## 玩法代码禁止绕过本单点自建 load();消费面一律走访问器,禁直读行集。
## headless --script 模式无 autoload——冒烟脚本显式实例化本类;加载失败 is_ready=false 加 fatal 封印,调用方须查。
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。
## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行),
## 表持有按域收进四容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域/
## presentation 表现层布局域),消费面经 db.<容器>.<表> 直连。
## 「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
## 冒烟脚本可显式实例化本类(headless --script 亦会加载 autoload——共享 .tres 上的 resolver 归最后装载的实例);
## 加载失败 is_ready=false 加 fatal 封印,调用方须查。
class_name ConfigDb
extends Node
const SCHEMA_VERSION := 1
const TABLE_MANIFEST := {
"action_economy": "action_economy.tres",
"variation": "variation.tres",
@@ -27,41 +29,28 @@ const TABLE_MANIFEST := {
"adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点)
"feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
"forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面
"battle_layout": "battle_layout.tres", # BattleLayoutTable——战斗屏分区几何与明细带高度单行
}
var action_economy: ActionEconomyTable
var variation: VariationTable
var status_effects: StatusEffectTable
var board_gen: BoardGenTable
var enemy: EnemyTable
var skill: SkillTable
var hero: HeroTable
var reward: RewardTable
var settlement: SettlementTable
var level: LevelTable
var difficulty: DifficultyTable
var level_gen_params: LevelGenParams # 参数档类型化持用(生成器只吃参数档、不认关卡号——卡① 契约 6)
# ShopTable 类型化持用(schema class_name 已已入库;
# 消费面一律走下方 shop_* 访问器,禁直读行集)
var shop: ShopTable = null
# MetaTable 类型化持用(schema 与表同源已入库)
var meta: MetaTable
var meta_tree: MetaTreeTable # 类型化持用(消费面走 tree_node 访问器)
var treasure_items: TreasureItemTable # 类型化持用(消费面走 treasure_* 访问器)
var adventure: AdventureTable
var feats: FeatTable # 类型化持用(消费面走 feat_* 访问器)
var forge: ForgeTable # 锻造工坊配置(默认两轴+六路线行族——消费面走 forge_* 访问器)
var combat: ConfigDomainCombat # 战斗编排域七表(board_gen/variation/status_effects/enemy/skill/hero/settlement)
var run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
var presentation: ConfigDomainPresentation # 表现层布局域一表(battle_layout)
var is_ready := false
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
var _resolver: ConfigDbResolver # R1 难度行解析协作类(门面公有签名不变)
var _resolver: ConfigDbResolver # R1 难度行解析协作类(门面公有签名不变;load_tables 装配段注入三表)
var _validator := ConfigDbCrossValidator.new() # 跨表校验协作类(无状态——声明期即建)
var _reload_gate := false # 数据配置 EC8——战斗结算 唯一调用方
func _init() -> void:
combat = ConfigDomainCombat.new()
run = ConfigDomainRun.new()
crossrun = ConfigDomainCrossrun.new()
presentation = ConfigDomainPresentation.new()
_resolver = ConfigDbResolver.new(push_diag,
func() -> Variant: return board_gen.rows, func() -> Variant: return enemy.rows, func() -> Variant: return difficulty) # 协作类持门面引用(诊断/致命通道回呼)
func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.difficulty) # 协作类持门面引用(诊断/致命通道回呼)
func push_diag(severity: String, category: String, message: String) -> void:
@@ -132,47 +121,38 @@ func load_tables(root: String, collect_only: bool) -> bool:
if has_errors and not collect_only:
# 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建
return false
action_economy = loaded.get("action_economy")
variation = loaded.get("variation")
status_effects = loaded.get("status_effects")
board_gen = loaded.get("board_gen")
enemy = loaded.get("enemy")
skill = loaded.get("skill")
hero = loaded.get("hero")
reward = loaded.get("reward")
settlement = loaded.get("settlement")
level = loaded.get("level")
difficulty = loaded.get("difficulty")
level_gen_params = loaded.get("level_gen_params")
shop = loaded.get("shop")
meta = loaded.get("meta")
treasure_items = loaded.get("treasure_items")
adventure = loaded.get("adventure")
feats = loaded.get("feats")
forge = loaded.get("forge")
meta_tree = loaded.get("meta_tree")
# 容器装配(表行按域收进四容器+resolver 注入)归私有协函数——load_tables 只留加载/校验/致命门面
_assign_loaded(loaded)
is_ready = not has_errors
return not has_errors
# ---- relic 访问器(消费面一律走本组,禁直读行集)----
func feat_row(feat_id: String) -> Dictionary:
return feats.row_by_id(feat_id) if feats != null else {}
# 规则改写型特性带令牌构建源(池接线——CurioDeck.build 消费;行 rarity→投放档映射)
func feat_rows() -> Array:
return feats.rows if feats != null else []
# 天赋树节点行(MetaProgression 树段/营地树 UI 取数口)
func tree_node(node_id: String) -> Dictionary:
return meta_tree.row_by_id(node_id) if meta_tree != null else {}
func tree_rows() -> Array:
return meta_tree.rows if meta_tree != null else []
# 容器装配协函数:20 表成员按域收进四容器(2026-10-27 全清设计定稿,2026-10-31 增表现层布局域)
func _assign_loaded(loaded: Dictionary) -> void:
combat.board_gen = loaded.get("board_gen")
combat.variation = loaded.get("variation")
combat.status_effects = loaded.get("status_effects")
combat.enemy = loaded.get("enemy")
combat.skill = loaded.get("skill")
combat.hero = loaded.get("hero")
combat.settlement = loaded.get("settlement")
run.level = loaded.get("level")
run.difficulty = loaded.get("difficulty")
run.level_gen_params = loaded.get("level_gen_params")
run.reward = loaded.get("reward")
run.adventure = loaded.get("adventure")
run.action_economy = loaded.get("action_economy")
run.shop = loaded.get("shop")
crossrun.meta = loaded.get("meta")
crossrun.meta_tree = loaded.get("meta_tree")
crossrun.treasure_items = loaded.get("treasure_items")
crossrun.feats = loaded.get("feats")
crossrun.forge = loaded.get("forge")
presentation.battle_layout = loaded.get("battle_layout")
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
combat.board_gen._resolver = _resolver
combat.enemy._resolver = _resolver
run.difficulty._resolver = _resolver
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
@@ -181,263 +161,12 @@ func _cross_validate(loaded: Dictionary) -> void:
_validator.cross_validate(loaded, push_diag)
# 池模板 × 技能行跨表校验(冒烟直验独立入口——签名与消费面零变化,实现归协作类)
func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable, status_table: StatusEffectTable = null) -> Array:
# 池模板 × 技能行跨表校验(冒烟直验独立入口——签名与消费面零变化,实现归协作类;门面私有——
# 消费面经门面者改 _ 前缀直调,cross_validator 直验入口公开面不变)
func _validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable, status_table: StatusEffectTable = null) -> Array:
return _validator.validate_skill_pool_links(skill_table, enemy_table, status_table)
# ---- R1 难度行解析(ceil 向上取整保证偏难;域越界与内容缺口 Fatal 退出)----
# 解析本体:ConfigDbResolver(core/config_db_resolver.gd)承载——
# 含 _resolve_progress/_ceil_key 与 R1 告警去重注册表;门面同名方法委托,越域 Fatal 语义一致。
func resolve_board_gen(L: int, D: int) -> Dictionary:
return _resolver.resolve_board_gen(L, D)
func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]:
return _resolver.resolve_enemy_rowset(L, D, row_kind) # 行集数组形态(非单行 Dictionary;D 维带寻址)
# 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数,越域值 clamp(GDD Edge Case——组合事故非配置事故)
func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary:
return _resolver.resolve_difficulty_modifiers(L, D)
# ---- 地形变异 variant_pool_gate:候选池门控(∅ board_types = 通配;字段名对齐
# board_type 词表,无 biome_tags)----
# 门控第三维 = 节点身份(全局作用域变异):scope=global 行仅 boss/elite 节点入池,
# 其余节点(battle 等)静默跳过——门控兜底 P0 口径(零报错零警告);elite 系前向词位(首发零表行)。
# node_type 缺省 "" = 旧双参调用口径(global 行跳过)——既有测试/冒烟调用零扰动。
func variant_pool(level_index: int, board_type: String, node_type: String = "") -> Array:
var pool: Array[Dictionary] = []
for row in variation.rows:
if String(row.get("scope", "region")) == "global" \
and node_type != "boss" and node_type != "elite":
continue
if int(row.get("unlock_level", 999)) > level_index:
continue
var tags: Array[Variant] = row.get("board_types", [])
if tags.is_empty():
pool.append(row)
continue
if tags.has(board_type):
pool.append(row)
return pool
# 全局型单行决议(战斗编排装板期消费——访问器纪律:消费面禁直扫表):
# boss/elite 节点取候选池内首个 scope=global 行(首发批全局池恒恰一行;多行为配置越界态,
# 按表序取首行确定性兜底);空 = 无全局型(调用方回落 region 布置——兜底回落合法态)
func global_variant_row(level_index: int, board_type: String, node_type: String) -> Dictionary:
if node_type != "boss" and node_type != "elite":
return {}
for row in variant_pool(level_index, board_type, node_type):
if String(row.get("scope", "region")) == "global":
return row
return {}
func status_effect_row(effect_id: String) -> Dictionary:
return status_effects.row_by_id(effect_id)
# ---- 关卡行集访问器(表访问收口——level_runner/meta_progression/adventure_runner
# 不直扫 db.level.rows;消费面一律走本访问器,行集解析单点归 LevelTable)----
# 关卡行单点读取(键 = level_index,缺行返回空字典——消费方按既有空行防御路径处理)
func level_row(level_index: int) -> Dictionary:
return level.row_by_level_index(level_index) if level != null else {}
# 参数档单点读取(节点图生成器核心批 卡①——键 = level_index,缺行返回空字典;调用方据此产图,生成器本体不读关卡号)
func level_gen_params_row(level_index: int) -> Dictionary:
return level_gen_params.row_by_level_index(level_index) if level_gen_params != null else {}
# 序列总关数(AdventureRunner levels_total / MetaProgression 关卡完成累加域的取数口径)
func level_count() -> int:
return level.rows.size() if level != null else 0
# ---- ShopTable 消费面(行集解析单点——runner/战斗编排禁自扫表)----
# ── 秘宝访问器(消费面一律走访问器,禁直读行集)──
# 活行集(非 retired 非 deferred)——投放校验/奖励节点池/呈现可用性的唯一取数口径
func treasure_item_row(item_id: String) -> Dictionary:
return treasure_items.row_by_id(item_id) if treasure_items != null else {}
# 过渡渠道活行(form 空 = 切片期过渡形态)——掉落投放/停点池/工坊解锁取数口径;同调行(form 非空)
# 永不入过渡渠道
func treasure_transitional_rows() -> Array:
return treasure_items.transitional_rows() if treasure_items != null else []
# 同调行集(form 非空——装配覆盖层/效果接线的取数口径)
func treasure_attunement_rows() -> Array:
return treasure_items.attunement_rows() if treasure_items != null else []
# 活行词表(掉落投放 effect_id 白名单——过渡渠道口径)
# ── 同调槽位数=树同调位节点点亮数,判定源=Attunement.slots(无 attunement_growth 谓词表访问器);
# boss_kill_count/clear_count 计数域保留——非槽位硬消费在册 ──
# 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} ·
# level_gp_bonus {level_index, bonus} · products {product_id, display_name, attr, flat_value,
# base_price, price_increment} · price_level_bonus {level_index, bonus}。
# 行族经 ShopTable 同名数组属性读取(.get 容缺——表已入库前查询恒空,掉落按 0 兑付的兜底在调用方)。
func shop_gp_drop_row(tier: String) -> Dictionary:
for row in _shop_rows("gp_drops"):
if String(row.get("tier", "")) == tier:
return row
return {}
func shop_level_gp_bonus(level_index: int) -> int:
for row in _shop_rows("level_gp_bonus"):
if int(row.get("level_index", -1)) == level_index:
return int(row.get("bonus", 0))
return 0
func shop_price_level_bonus(level_index: int) -> int:
for row in _shop_rows("price_level_bonus"):
if int(row.get("level_index", -1)) == level_index:
return int(row.get("bonus", 0))
return 0
func shop_products() -> Array:
return _shop_rows("products")
func shop_product_row(product_id: String) -> Dictionary:
for row in _shop_rows("products"):
if String(row.get("product_id", "")) == product_id:
return row
return {}
# ── 共享池货架访问器(M-4——消费面一律走访问器,禁直读行集)──
# 货架行族({kind_id, display_name, base_price}——行序即抽签/呈现稳定序)
func shop_pool_rows() -> Array:
return _shop_rows("pool_shelf")
func shop_pool_row(kind_id: String) -> Dictionary:
for row in _shop_rows("pool_shelf"):
if String(row.get("kind_id", "")) == kind_id:
return row
return {}
# 乘法价格因子(provisional 投值——0 = 表未投值,消费方按 fail-fast 域校验拦截)
func shop_price_a() -> float:
return float(shop.price_a) if shop != null else 0.0
func shop_price_b() -> float:
return float(shop.price_b) if shop != null else 0.0
# 行族数组读取(ShopTable 属性名 = 行族名;表未已入库/属性缺失 = 空集非错误态)
func _shop_rows(family: String) -> Array:
if shop == null:
return []
var arr = shop.get(family)
return arr if arr is Array else []
# ---- MetaTable 消费面(行集解析单点——MetaProgression/呈现层禁自扫表)----
# 行族四轴(数据契约,行集归 meta.tres,数值全 provisional):
# forge_products {attr, display_name, flat_value, base_price, price_ratio_pct} ·
# boss_keystone {level_index, first_kill, repeat_kill} ·
# workshop_items {item_id, display_name, clear_threshold} ·
# conversion {attr, per_level_shift_pct}。
func meta_boss_keystone_row(level_index: int) -> Dictionary:
for row in _meta_rows("boss_keystone"):
if int(row.get("level_index", -1)) == level_index:
return row
return {}
# 秘宝解锁混合制挂 treasure_items 行内(无 meta_workshop_items 访问器);
# 秘宝解锁投影面=MetaProgression.treasure_shop_status
func meta_conversion_row(attr: String) -> Dictionary:
for row in _meta_rows("conversion"):
if String(row.get("attr", "")) == attr:
return row
return {}
# Y 刷新升级行单点读取(键 = 等级,缺行返回空字典=未购/已满级合法态)
func meta_attune_refresh_row(level: int) -> Dictionary:
for row in _meta_rows("attune_refresh_upgrade"):
if int(row.get("level", -1)) == level:
return row
return {}
# 行族数组读取(MetaTable 属性名 = 行族名;表缺失/属性缺失 = 空集非错误态)
func _meta_rows(family: String) -> Array:
if meta == null:
return []
var arr = meta.get(family)
return arr if arr is Array else []
# ---- 锻造工坊(ForgeTable 消费面:两根类型轴默认值+六路线行族;禁自扫行集)----
# 设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6:默认值归本表常驻持有(hero.tres 两轴已删——
# 契约 2);路线行族=攻防数值成长唯一数据面(契约 1)。数值全 provisional 归 M4。
# 两根类型轴默认值(表未已入库=裸体态兜底 normal/light——与 ForgeTable 缺省值同口径)
func forge_default_attack_type() -> String:
return String(forge.default_attack_type) if forge != null else "normal"
func forge_default_armor_type() -> String:
return String(forge.default_armor_type) if forge != null else "light"
# 六路线行族(副本——调用方禁改)
func forge_routes() -> Array:
return forge.routes.duplicate() if forge != null else []
# 路线行单点(键 route_id;缺行=空字典)
func forge_route(route_id: String) -> Dictionary:
for row in forge_routes():
if String(row.get("route_id", "")) == route_id:
return row
return {}
# 按类型轴反查武器路线(attack_type → 路线行;甲路线 id=甲型同值的显式化入口,避免消费侧依赖该巧合)
func forge_weapon_route_by_type(attack_type: String) -> Dictionary:
for row in forge_routes():
if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) == attack_type:
return row
return {}
# 按类型轴反查护甲路线(armor_type → 路线行——入战 AC 三层之「甲型基础 AC」取数口)
func forge_armor_route_by_type(armor_type: String) -> Dictionary:
for row in forge_routes():
if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) == armor_type:
return row
return {}
# 技能行单点读取(敌人技能池批——单位容器执行上报 / 战斗结算兑现的消费面)
func skill_row(skill_id: String) -> Dictionary:
return skill.row_by_id(skill_id)
# ---- 数据配置 EC8:debug 热重载门(战斗结算 唯一调用方;切片实现 = 门标志 + 拒绝语义)----
func set_reload_gate(gated: bool) -> void:
+88 -47
View File
@@ -11,21 +11,43 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
var st_tab: SettlementTable = loaded.get("settlement")
if st == null or rt == null or st_tab == null:
return # 单表校验已报错,不连锁
# ①c F2 投放维度覆盖校验不启用:挖格不掉道具,秘宝
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
# 静态校验。
var effect_ids := _effect_ids_of(st)
_cv_reward_dangling(rt, effect_ids, push_diag) # ① 增益词表悬空
_cv_reward_treasure(loaded, rt, push_diag) # ①b aux 投放行双表
_cv_poolmap(st_tab, effect_ids, push_diag) # ② 池映射悬空
_cv_source_labels(st_tab, push_diag) # ③ 来源标签键集
_cv_skill_pool(loaded, push_diag) # ⑤ 技能池引用完整性
_cv_shelf_pool(loaded, push_diag) # ⑥ 商店货架 × 冒险共享池
_cv_counter_closure(loaded, push_diag) # ⑦ 计数器引用闭合
## 增益状态词表全集(effect_id → true)
func _effect_ids_of(st: StatusEffectTable) -> Dictionary:
var effect_ids := {}
for row in st.rows:
effect_ids[row.get("effect_id", "")] = true
# ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)。
# 例外:gp_gain/gp_loss = GP 战斗内缓冲(escrow)通道的路由键
# (战斗结算 _apply_gp_row 兑付——不入增益状态容器),词表封闭按字面豁免悬空引用校验。
return effect_ids
## ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)。
## 例外:gp_gain/gp_loss = GP 战斗内缓冲(escrow)通道的路由键
## (战斗结算 _apply_gp_row 兑付——不入增益状态容器),词表封闭按字面豁免悬空引用校验。
func _cv_reward_dangling(rt: RewardTable, effect_ids: Dictionary, push_diag: Callable) -> void:
for row in rt.rows:
if String(row.get("cell_type", "")) == RewardTable.TREASURE_ITEM_TYPE:
continue # aux 行 effect_id=item_id——归 ①b 双表校验(reward_dangling_treasure),不入增益词表面
var eff: String = row.get("effect_id", "")
if eff != "" and eff != "gp_gain" and eff != "gp_loss" and not effect_ids.has(eff):
push_diag.call("error", "dangling_effect", "RewardTable %s 引用不存在的 effect_id: %s" % [row.get("reward_id"), eff])
# ①b RewardTable aux 投放行双表校验(reward_dangling_treasure):aux 行 effect_id = item_id,
# 须命中 TreasureItemTable 活行(非 retired 非 deferred——罗盘退役/失义件不投放)。retired 行被引用=退役
# 后仍投放(连带错误),同拦。
## ①b RewardTable aux 投放行双表校验(reward_dangling_treasure):aux 行 effect_id = item_id,
## 须命中 TreasureItemTable 活行(非 retired 非 deferred——罗盘退役/失义件不投放)。retired 行被引用=退役
## 后仍投放(连带错误),同拦。
func _cv_reward_treasure(loaded: Dictionary, rt: RewardTable, push_diag: Callable) -> void:
var treasure_live := {}
var treasure_tab = loaded.get("treasure_items")
if treasure_tab != null:
@@ -39,27 +61,36 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
continue
if not treasure_live.has(treasure_eff):
push_diag.call("error", "reward_dangling_treasure", "RewardTable %s aux 行引用无效道具 %s(retired/deferred/不存在): %s" % [row.get("reward_id"), treasure_eff, "双表校验"])
# ①c F2 投放维度覆盖校验不启用:挖格不掉道具,秘宝
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
# 静态校验。
# ② SettlementTable.effect_pool_map 悬空
## ② SettlementTable.effect_pool_map 悬空
func _cv_poolmap(st_tab: SettlementTable, effect_ids: Dictionary, push_diag: Callable) -> void:
for eff in st_tab.core.effect_pool_map.keys():
if not effect_ids.has(eff):
push_diag.call("error", "poolmap_dangling", "SettlementTable.effect_pool_map 悬空 effect_id: %s" % eff)
# ③ source_label_map 键集封闭性(键 = 增益状态 source_class ∪ {enemy_action, hero_attack, seal_miss})
## ③ source_label_map 键集封闭性(键 = 增益状态 source_class ∪ {enemy_action, hero_attack, seal_miss})
func _cv_source_labels(st_tab: SettlementTable, push_diag: Callable) -> void:
var required_sources := ["safe_cell", "seal_hit", "mine_hit", "clear_reward", "hero_attack", "enemy_action", "seal_miss"]
for s in required_sources:
if not st_tab.core.source_label_map.has(s):
push_diag.call("error", "segorder_violation", "source_label_map 缺必需键: %s" % s)
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
## ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
func _cv_skill_pool(loaded: Dictionary, push_diag: Callable) -> void:
var en: EnemyTable = loaded.get("enemy")
var skt = loaded.get("skill")
if skt != null and en != null:
for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")):
push_diag.call("error", e.category, e.message)
# ⑥ 商店货架 × 冒险共享池跨表校验(M-4——次数上限域):
# pool_shelf kind 须命中 adventure.CURIO_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
# 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
## ⑥ 商店货架 × 冒险共享池跨表校验(M-4——次数上限域):
## pool_shelf kind 须命中 adventure.CURIO_KINDS 且为有限库存种(cap ∈ [1,99]——gp 无上限种/牌库外种
## 入货架 = 库存账本无锚,加载致命;gp 袋负交易切边/规则改写型特性三轴切边由 ShopTable 词表封闭另拦)
func _cv_shelf_pool(loaded: Dictionary, push_diag: Callable) -> void:
var adv = loaded.get("adventure")
var shp = loaded.get("shop")
if adv != null and shp != null:
@@ -78,34 +109,42 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
if quota < 1:
push_diag.call("error", "shelf_zero_quota", "ShopTable.pool_shelf kind %s 令牌配额为 0(牌库空种入货架 = 库存无锚)" % kind)
# ⑦ 计数器引用闭合(卡② V9——读点 id 必命中同名写入行,否则加载致命):
# 声明集 = feats + treasure 各行 triggers 内 kind=counter_add 的 do.counter_id;
# 读点 = when fact=counter 的 id、do.value_counter、kind=counter_spend 的 counter_id。
var feats_tab = loaded.get("feats")
## ⑦ 计数器引用闭合(卡② V9——读点 id 必命中同名写入行,否则加载致命):
## 声明集 = feats + treasure 各行 triggers 内 kind=counter_add 的 do.counter_id;
## 读点 = when fact=counter 的 id、do.value_counter、kind=counter_spend 的 counter_id。
func _cv_counter_closure(loaded: Dictionary, push_diag: Callable) -> void:
var trig_rows: Array[Variant] = []
var feats_tab = loaded.get("feats")
if feats_tab != null:
for r in feats_tab.rows:
trig_rows.append(r)
var treasure_tab = loaded.get("treasure_items")
if treasure_tab != null:
for r in treasure_tab.rows:
trig_rows.append(r)
var counter_decls := _counter_decls_of(trig_rows)
_cv_counter_reads(trig_rows, counter_decls, push_diag)
## 计数器声明集收集:任一 do.counter_id 行(counter_add 直写、forgive_seal_miss 经 _apply_counter_hit 写——皆写入行)
func _counter_decls_of(trig_rows: Array) -> Dictionary:
var counter_decls := {}
for row in trig_rows:
for tr_v in (row.get("triggers", []) as Array):
var dd: Dictionary[Variant, Variant] = (tr_v as Dictionary).get("do", {}) as Dictionary[Variant, Variant]
# 声明集=任一 do.counter_id 行(counter_add 直写、forgive_seal_miss 经 _apply_counter_hit 写——皆写入行)
if dd.has("counter_id"):
counter_decls[String(dd.counter_id)] = true
return counter_decls
## 计数器读点校验:when counter id/do.value_counter/counter_spend 的 counter_id 必命中声明集
func _cv_counter_reads(trig_rows: Array, counter_decls: Dictionary, push_diag: Callable) -> void:
for row in trig_rows:
var rid := String(row.get("feat_id", row.get("item_id", "")))
for tr_v in (row.get("triggers", []) as Array):
var tr: Dictionary[Variant, Variant] = tr_v as Dictionary[Variant, Variant]
for c_v in (tr.get("when", []) as Array):
var c: Dictionary[Variant, Variant] = c_v as Dictionary[Variant, Variant]
if String(c.get("fact", "")) == "counter" and c.has("id"):
var rd := String(c.id)
if not counter_decls.has(rd):
push_diag.call("error", "counter_dangling_ref", "%s when counter 读点无同名写入行: %s(引用闭合)" % [rid, rd])
_check_counter_when(rid, tr, counter_decls, push_diag)
var do2: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary[Variant, Variant]
if do2.has("value_counter") and not counter_decls.has(String(do2.value_counter)):
push_diag.call("error", "counter_dangling_ref", "%s value_counter 读点无同名写入行: %s" % [rid, String(do2.value_counter)])
@@ -113,6 +152,16 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
push_diag.call("error", "counter_dangling_ref", "%s counter_spend 无同名写入行: %s" % [rid, String(do2.counter_id)])
## when counter 读点校验(fact=counter 条件的 id 必命中声明集)
func _check_counter_when(rid: String, tr: Dictionary, counter_decls: Dictionary, push_diag: Callable) -> void:
for c_v in (tr.get("when", []) as Array):
var c: Dictionary[Variant, Variant] = c_v as Dictionary[Variant, Variant]
if String(c.get("fact", "")) == "counter" and c.has("id"):
var rd := String(c.id)
if not counter_decls.has(rd):
push_diag.call("error", "counter_dangling_ref", "%s when counter 读点无同名写入行: %s(引用闭合)" % [rid, rd])
# 池模板 × 技能行跨表校验(加载期致命;返回错误列表——ConfigDB 诊断通道与冒烟直验共用):
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1、妨害技 ≤1——proposal §2.1 结构保证 +
# proposal-status-vocabulary §1.2)+ 施放类 side 守卫(增益引用 enemy 侧 / 妨害引用 hero 侧状态行)。
@@ -141,33 +190,25 @@ func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable,
errs.append_array(_pool_cast_side_errs(skill_table, status_table)) # 施放类 side 守卫(buff→敌方侧/debuff→主角侧,防反向挂接——全表恰一次)
for row in enemy_table.rows:
var eid: String = String(row.get("enemy_id", ""))
var basic_n := 0
var buff_n := 0
var debuff_n := 0
var uncond_special_n := 0
var pool_n := {"basic": 0, "buff": 0, "debuff": 0, "uncond_special": 0}
for entry in row.get("pool", []):
var sid: String = String(entry.get("skill_id", ""))
if not skill_types.has(sid):
errs.append({"category": "dangling_skill", "message": "EnemyTable %s 池引用不存在的 skill_id: %s(悬空致命)" % [eid, sid]})
continue
match skill_types[sid]:
"basic":
basic_n += 1
"buff":
buff_n += 1
"debuff":
debuff_n += 1
if pool_n.has(skill_types[sid]):
pool_n[skill_types[sid]] += 1
# 无条件特殊技计数(普攻不计;trigger 缺省行计数——触发条件批 2026-10-26 池构成纪律)
if skill_types[sid] != "basic" and not (skill_table.row_by_id(sid) if skill_table.has_method("row_by_id") else {}).is_empty():
if skill_types[sid] != "basic":
var srow_c: Dictionary[Variant, Variant] = skill_table.row_by_id(sid) if skill_table.has_method("row_by_id") else {}
if not srow_c.has("trigger"):
uncond_special_n += 1
if basic_n != 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, basic_n]})
if buff_n > 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, buff_n]})
if debuff_n > 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池妨害技 > 1 (%d)——每池妨害 ≤1 纪律(新效应词汇批 2026-09-19)" % [eid, debuff_n]})
if uncond_special_n < 1:
if not srow_c.is_empty() and not srow_c.has("trigger"):
pool_n["uncond_special"] += 1
if pool_n["basic"] != 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, pool_n["basic"]]})
if pool_n["buff"] > 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, pool_n["buff"]]})
if pool_n["debuff"] > 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池妨害技 > 1 (%d)——每池妨害 ≤1 纪律(新效应词汇批 2026-09-19)" % [eid, pool_n["debuff"]]})
if pool_n["uncond_special"] < 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池无条件特殊技行数 = 0——每池至少一行无条件特殊技纪律(触发条件批 2026-10-26)" % [eid]})
return errs
+73 -41
View File
@@ -43,11 +43,7 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
return []
var rows: Array[Dictionary] = []
rows.assign(_enemy_rows_get.call())
var level_keys: Array[int] = []
for row in rows:
var lv := int(row.get("level_index", 0))
if not level_keys.has(lv):
level_keys.append(lv)
var level_keys: Array[int] = _level_keys_of(rows)
if level_keys.is_empty():
_diag.call("fatal", "structural", "enemy 行集为空——锚行校验应已拦截")
return []
@@ -58,12 +54,7 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
_warn_resolve("enemy", "resolve_level_clamp", "enemy 请求 (%d,%d) clamp 解析(超出最大关卡取最大值)→ 关卡 %d" % [L, D, l_star], l_star, D)
elif l_star != L:
_warn_resolve("enemy", "resolve_level_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ 关卡 %d" % [L, D, l_star], l_star, D)
var band_keys: Array[int] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind:
var bv := int(row.get("depth_band", -1))
if not band_keys.has(bv):
band_keys.append(bv)
var band_keys: Array[int] = _band_keys_of(rows, l_star, row_kind)
if band_keys.is_empty():
return []
band_keys.sort()
@@ -73,6 +64,32 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
_warn_resolve("enemy", "resolve_depth_clamp", "enemy 请求 (%d,%d) clamp 解析(超出顶带取顶带)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
elif d_star != D:
_warn_resolve("enemy", "resolve_depth_ceil", "enemy 请求 (%d,%d) ceil 解析(向上取整保证偏难)→ (%d,band %d)" % [L, D, l_star, d_star], l_star, d_star)
return _band_rows(rows, l_star, row_kind, d_star)
## 行集关卡键收集(纯行扫描——告警发射序不变)
func _level_keys_of(rows: Array) -> Array[int]:
var level_keys: Array[int] = []
for row in rows:
var lv := int(row.get("level_index", 0))
if not level_keys.has(lv):
level_keys.append(lv)
return level_keys
## 带键收集(纯行扫描——l_star × row_kind 的 depth_band 去重集)
func _band_keys_of(rows: Array, l_star: int, row_kind: String) -> Array[int]:
var band_keys: Array[int] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind:
var bv := int(row.get("depth_band", -1))
if not band_keys.has(bv):
band_keys.append(bv)
return band_keys
## 带行收集(纯行扫描——命中带全部行、按表文件行序)
func _band_rows(rows: Array, l_star: int, row_kind: String, d_star: int) -> Array[Dictionary]:
var all_out: Array[Dictionary] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star and String(row.get("row_kind", "")) == row_kind \
@@ -108,11 +125,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Variant
if L < 1 or D < 0:
_diag.call("fatal", "request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
return {}
var level_keys: Array[int] = []
for row in rows:
var lv := int(row.get("level_index", 0))
if not level_keys.has(lv):
level_keys.append(lv)
var level_keys: Array[int] = _level_keys_of(rows)
if level_keys.is_empty():
_diag.call("fatal", "structural", "%s 行集为空——锚行校验应已拦截" % table_id)
return {}
@@ -121,40 +134,59 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Variant
var l_clamped := l_star < 0
if l_clamped:
l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶(可取范围内向上取整,超出用最大值)
var depth_keys: Array[int] = _depth_keys_of(rows, l_star)
depth_keys.sort()
var d_star := _ceil_key(depth_keys, D)
var d_clamped := d_star < 0
if d_clamped:
d_star = int(depth_keys[-1])
var missing := _missing_label(l_clamped, d_clamped, l_star != L, d_star != D)
if missing != "":
_emit_missing_warning(table_id, missing, [l_star, d_star], [L, D])
for row in rows:
if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star:
return row
return {}
## 层键收集(纯行扫描——l_star 行的 node_depth 去重集)
func _depth_keys_of(rows: Array, l_star: int) -> Array[int]:
var depth_keys: Array[int] = []
for row in rows:
if int(row.get("level_index", 0)) == l_star:
var dv := int(row.get("node_depth", 0))
if not depth_keys.has(dv):
depth_keys.append(dv)
depth_keys.sort()
var d_star := _ceil_key(depth_keys, D)
var d_clamped := d_star < 0
if d_clamped:
d_star = int(depth_keys[-1])
var missing := ""
return depth_keys
## 解析偏移形态标签(clamp/ceil × level/depth/both 六形态——空串 = 精确命中;
## l_ceil/d_ceil = 解析值 ≠ 请求值)
func _missing_label(l_clamped: bool, d_clamped: bool, l_ceil: bool, d_ceil: bool) -> String:
var label := ""
if l_clamped and d_clamped:
missing = "both_clamp"
label = "both_clamp"
elif l_clamped:
missing = "level_clamp"
label = "level_clamp"
elif d_clamped:
missing = "depth_clamp"
elif l_star != L and d_star != D:
missing = "both_ceil"
elif l_star != L:
missing = "level_ceil"
elif d_star != D:
missing = "depth_ceil"
if missing != "":
var dedup_key := "%s|%s|%d|%d" % [table_id, missing, l_star, d_star]
if not _warn_dedup.has(dedup_key):
_warn_dedup[dedup_key] = true
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
_diag.call("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, L, D, verb, l_star, d_star, missing])
for row in rows:
if int(row.get("level_index", 0)) == l_star and int(row.get("node_depth", 0)) == d_star:
return row
return {}
label = "depth_clamp"
elif l_ceil and d_ceil:
label = "both_ceil"
elif l_ceil:
label = "level_ceil"
elif d_ceil:
label = "depth_ceil"
return label
## 偏移告警发射(四元组去重注册表语义逐字——R1 既有去重;
## req/resolved = [L, D]/[l_star, d_star] 进度坐标对)
func _emit_missing_warning(table_id: String, missing: String, resolved: Array, req: Array) -> void:
var dedup_key := "%s|%s|%d|%d" % [table_id, missing, resolved[0], resolved[1]]
if not _warn_dedup.has(dedup_key):
_warn_dedup[dedup_key] = true
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
_diag.call("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, req[0], req[1], verb, resolved[0], resolved[1], missing])
# 取 sorted_keys 中第一个 >= request 的键;无(请求高于最大行)返回 -1 → 调用方 Fatal
+12
View File
@@ -0,0 +1,12 @@
## 战斗域容器(D2 域容器——config_db 表持有按域收敛):战斗编排域七表类型化持有。
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainCombat
extends Resource
var board_gen: BoardGenTable
var variation: VariationTable
var status_effects: StatusEffectTable
var enemy: EnemyTable
var skill: SkillTable
var hero: HeroTable
var settlement: SettlementTable
+1
View File
@@ -0,0 +1 @@
uid://dpy0a6fcggbwe
+11
View File
@@ -0,0 +1,11 @@
## 跨局域容器(D2 域容器——config_db 表持有按域收敛):跨局元进度域五表类型化持有
## (跨局循环=单局之外的长线进度域)。零方法零行为——纯持有结构;
## 「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainCrossrun
extends Resource
var meta: MetaTable
var meta_tree: MetaTreeTable
var treasure_items: TreasureItemTable
var feats: FeatTable
var forge: ForgeTable
+1
View File
@@ -0,0 +1 @@
uid://du0gxv0lnrxnp
+6
View File
@@ -0,0 +1,6 @@
## 表现层布局域容器(D2 域容器——config_db 表持有按域收敛):战斗屏布局一表类型化持有。
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainPresentation
extends Resource
var battle_layout: BattleLayoutTable
@@ -0,0 +1 @@
uid://bay1b1s3lrd1e
+12
View File
@@ -0,0 +1,12 @@
## 冒险域容器(D2 域容器——config_db 表持有按域收敛):冒险 runner 域七表类型化持有。
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
class_name ConfigDomainRun
extends Resource
var level: LevelTable
var difficulty: DifficultyTable
var level_gen_params: LevelGenParams
var reward: RewardTable
var adventure: AdventureTable
var action_economy: ActionEconomyTable
var shop: ShopTable
+1
View File
@@ -0,0 +1 @@
uid://jaxlqd8ml3co
+17 -17
View File
@@ -29,13 +29,13 @@ func reset() -> void:
# relic 令牌档 = 行 rarity 直映射,词表外 transitional 值回退映射 rarest→rare/其余→excellent)
func build() -> Array:
var out: Array[Variant] = []
for kind in db.adventure.CURIO_KINDS:
for tier in db.adventure.TIERS:
for i in int(db.adventure.curio_quota(String(kind), String(tier))):
for kind in db.run.adventure.CURIO_KINDS:
for tier in db.run.adventure.TIERS:
for i in int(db.run.adventure.curio_quota(String(kind), String(tier))):
out.append({"kind": String(kind), "tier": String(tier)})
for row in db.feat_rows():
for row in db.crossrun.feats.rows:
var rraw := String(row.get("rarity", ""))
var rtier := rraw if db.adventure.TIERS.has(rraw) else ("rare" if rraw == "rarest" else "excellent")
var rtier := rraw if db.run.adventure.TIERS.has(rraw) else ("rare" if rraw == "rarest" else "excellent")
out.append({"kind": "relic", "tier": rtier, "feat_id": String(row.get("feat_id", ""))})
return out
@@ -73,7 +73,7 @@ func pool_stock(kind: String) -> int:
# 读数面 = 商店买走 vs 奖励领走 vs 枯竭)
func stock_snapshot() -> Dictionary:
var out := {}
for row in db.shop_pool_rows():
for row in db.run.shop.pool_rows():
var kind := String(row.get("kind_id", ""))
out[kind] = pool_stock(kind)
return out
@@ -97,23 +97,23 @@ func consume_deck_token(kind: String, tier: String, feat_id: String = "") -> boo
func cap_ok(kind: String, feat_id: String = "") -> bool:
var cap := 99
if kind == "relic":
var row: Dictionary[Variant, Variant] = db.feat_row(feat_id)
var row: Dictionary[Variant, Variant] = db.crossrun.feats.row_by_id(feat_id)
cap = int(row.get("cap", 1)) if not row.is_empty() else 1
return taken.get("relic:" + feat_id, 0) < cap
if not db.adventure.CURIO_KINDS.has(kind):
if not db.run.adventure.CURIO_KINDS.has(kind):
return true
cap = int(db.adventure.curio_cap(kind))
cap = int(db.run.adventure.curio_cap(kind))
return int(taken.get(kind, 0)) < cap
# 专长凭证展示判定(四口径:一局限购一次/稀有只在概率不在价格/计步保底/触发清零。
# 调用即一次展示判定与记账——展示次数计 pity、配额与限购双闸)
func feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
if ticket_sold >= int(db.adventure.feat_ticket_limit):
if ticket_sold >= int(db.run.adventure.feat_ticket_limit):
return false # 限购满——不再触发(保底冻结)
if ticket_appeared >= int(db.adventure.feat_ticket_quota):
if ticket_appeared >= int(db.run.adventure.feat_ticket_quota):
return false # 配额满——本局不再出现
var offer: bool = ticket_pity >= int(db.adventure.feat_ticket_pity_step) or rg.randi_range(1, 100) <= int(db.adventure.feat_ticket_chance)
var offer: bool = ticket_pity >= int(db.run.adventure.feat_ticket_pity_step) or rg.randi_range(1, 100) <= int(db.run.adventure.feat_ticket_chance)
if offer:
ticket_pity = 0
ticket_appeared += 1
@@ -160,7 +160,7 @@ func _candidate_pool(channel: String) -> Array[Variant]:
func _draw_weighted(pool: Array[Variant], rg: RandomNumberGenerator) -> Array:
var out: Array[Variant] = []
var seen: Array[Variant] = []
var want := int(db.adventure.curio_candidate_count)
var want := int(db.run.adventure.curio_candidate_count)
while out.size() < want and not pool.is_empty():
var total := _pool_weight(pool)
if total <= 0:
@@ -180,13 +180,13 @@ func _draw_weighted(pool: Array[Variant], rg: RandomNumberGenerator) -> Array:
func _pool_weight(pool: Array[Variant]) -> int:
var total := 0
for tok in pool:
total += db.adventure.boss_feat_weight(String((tok as Dictionary).get("tier", "")))
total += db.run.adventure.boss_feat_weight(String((tok as Dictionary).get("tier", "")))
return total
func _weighted_index(pool: Array[Variant], roll: int) -> int:
for i in pool.size():
roll -= db.adventure.boss_feat_weight(String((pool[i] as Dictionary).get("tier", "")))
roll -= db.run.adventure.boss_feat_weight(String((pool[i] as Dictionary).get("tier", "")))
if roll <= 0:
return i
return 0
@@ -198,12 +198,12 @@ func _draw_exposure(pool: Array[Variant], rg: RandomNumberGenerator) -> Array:
_shuffle(pool, rg)
var sub: Array[Variant] = []
for tok in pool:
if db.adventure.tier_rank(String((tok as Dictionary).get("tier", ""))) >= db.adventure.tier_rank(db.adventure.TIER_EXCELLENT):
if db.run.adventure.tier_rank(String((tok as Dictionary).get("tier", ""))) >= db.run.adventure.tier_rank(db.run.adventure.TIER_EXCELLENT):
sub.append(tok)
_shuffle(sub, rg)
var out: Array[Variant] = []
var seen: Array[Variant] = []
var want := int(db.adventure.curio_candidate_count)
var want := int(db.run.adventure.curio_candidate_count)
var n_full := maxi(1, want - 1) if want >= 2 else want
while out.size() < want:
var src: Array[Variant] = sub if out.size() >= n_full and not sub.is_empty() else pool
+9 -9
View File
@@ -56,7 +56,7 @@ func rebind_level_runner(runner: LevelRunner, level_index: int) -> void:
func open_level_clear(payload_base: Dictionary) -> void:
var hp_before := _stats.hero_hp
var max_eff := _stats.effective_max_hp() # 回血封顶口径含调试浮窗 pct(同战斗内上限)
var heal := mini(int(floor(float(max_eff) * float(int(db.adventure.clear_heal_pct)) / 100.0)), max_eff - hp_before)
var heal := mini(int(floor(float(max_eff) * float(int(db.run.adventure.clear_heal_pct)) / 100.0)), max_eff - hp_before)
_stats.hero_hp = hp_before + heal
_bank.reset_x() # 碰三选一重置 X 余量(不结转)
_bank.reset_paid() # 付费刷计数随停点清零(对齐商店进店清零)
@@ -109,7 +109,7 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
return false
if kind == "feat_ticket":
return _redeem_feat_ticket() # T5 拿到即开——停点候选替换为专长三选一(零摘牌/不进持有集)
var value: int = int(db.adventure.curio_value(kind, tier))
var value: int = int(db.run.adventure.curio_value(kind, tier))
var hp_before := _stats.hero_hp
if not _apply_curio_pick(kind, tier, value, feat_id):
push_error("[停点件] pick_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
@@ -122,7 +122,7 @@ func pick_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
func _feat_payload_keys(feat_id: String) -> Dictionary:
if feat_id == "":
return {}
var row: Dictionary[Variant, Variant] = db.feat_row(feat_id)
var row: Dictionary[Variant, Variant] = db.crossrun.feats.row_by_id(feat_id)
return {"feat_id": feat_id, "feat_name": String(row.get("display_name", "")), "feat_archetype": String(row.get("archetype", ""))}
@@ -133,7 +133,7 @@ func _feat_payload_keys(feat_id: String) -> Dictionary:
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从牌库摘除该令牌(选中踢池;
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool:
if kind == "relic" and db.feat_row(feat_id).is_empty():
if kind == "relic" and db.crossrun.feats.row_by_id(feat_id).is_empty():
push_error("[停点件] relic 兑付失败——表无此件: %s" % feat_id)
return false # 先校验后摘牌(令牌零丢失)
if not _deck.consume_deck_token(kind, tier, feat_id):
@@ -172,7 +172,7 @@ func _apply_resource_pick(kind: String, value: int, feat_id: String) -> bool:
# 路由加算段(match 收敛进效果类型:属性值提升四类走 adventure_table.CURIO_APPLY_ROUTES 声明
# 路由(bonus 落点键),加新加算类令牌零代码)
func _apply_route_pick(kind: String, value: int) -> bool:
var sink := db.adventure.curio_apply_route(kind)
var sink := db.run.adventure.curio_apply_route(kind)
if sink == "":
return false
_journal.gain_bonus(sink, value, "curio_pick", kind) # 加算区单写原语(获得日记明细+汇总同拍)
@@ -229,7 +229,7 @@ func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") ->
if _deck.find_token(candidates, kind, tier, feat_id).is_empty():
push_error("[停点件] apply_node_curio 拒绝——令牌不在候选: %s|%s|%s" % [kind, tier, feat_id])
return false
var value: int = int(db.adventure.curio_value(kind, tier))
var value: int = int(db.run.adventure.curio_value(kind, tier))
if not _apply_curio_pick(kind, tier, value, feat_id):
push_error("[停点件] apply_node_curio 拒绝——未知件: %s|%s|%s" % [kind, tier, feat_id])
return false
@@ -348,10 +348,10 @@ func _finish_node_bonus(picked: String, value: int, tier: String = "", feat_id:
func consume_pool_purchase(kind: String, tier: String) -> bool:
if kind == "feat_ticket":
return _redeem_feat_ticket()
if not db.adventure.CURIO_KINDS.has(kind):
if not db.run.adventure.CURIO_KINDS.has(kind):
push_error("[停点件] 商店购买消费拒绝——件不在池词表: %s" % kind)
return false
return _apply_curio_pick(kind, tier, int(db.adventure.curio_value(kind, tier)))
return _apply_curio_pick(kind, tier, int(db.run.adventure.curio_value(kind, tier)))
# ---- 冒险级复位(宿主 _reset_curio_state 调用——停点半:池归 deck.reset、停点三态与刷新账清零)----
@@ -376,7 +376,7 @@ func restore_pending(bp_pending: Dictionary) -> void:
mig["feat_id"] = String((c as Dictionary).get("feat_id", ""))
migrated.append(mig)
else:
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
migrated.append({"kind": String(c), "tier": db.run.adventure.TIER_COMMON})
_pending = {"candidates": migrated}
var payload: Dictionary[Variant, Variant] = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
curio_choice_presented.emit(payload)
+52 -34
View File
@@ -294,7 +294,7 @@ func _hero_attack_ctx(t: Dictionary) -> Dictionary:
func _ctx_rewrites(t: Dictionary, target: Dictionary, armor: String, matchup: String) -> Dictionary:
var ac_hits: Array[Variant] = (t.context.get("fx_attack_hits", []) as Array)
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
var threshold: int = defender_ac(target) + int(db.settlement.damage_roll.attack_bonus)
var threshold: int = defender_ac(target) + int(db.combat.settlement.damage_roll.attack_bonus)
# armor_roll 发射点(敌护甲掷骰判定;免掷恒折扣订阅面)
var ar_hits := _fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)})
var trade_hit := 0
@@ -383,7 +383,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
var ac_hits: Array[Variant] = ctx["ac_hits"]
var roll_read: Dictionary[Variant, Variant] = ctx["roll_read"]
var atk_type: String = ctx["atk_type"]
var rolled: int = DamageMath.rolled_component(base_value, int(ctx["discount"]), bool(ctx["crit"]), int(db.settlement.damage_roll.crit_multiplier), int(ctx["cdelta"]))
var rolled: int = DamageMath.rolled_component(base_value, int(ctx["discount"]), bool(ctx["crit"]), int(db.combat.settlement.damage_roll.crit_multiplier), int(ctx["cdelta"]))
var payload: Dictionary[Variant, Variant] = {"target": String(target.display_name), "target_id": String(target.get("enemy_id", ""))}
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
payload["roll"] = int(roll_read.roll)
@@ -392,7 +392,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
if bool(ctx["crit"]):
payload["crit"] = true
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_offense"), clamped_pool("enemy_defense"))
var landed: int = maxi(int(db.settlement.damage_roll.damage_floor), pooled)
var landed: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), pooled)
# 加法族:英雄侧 rider(enemy_skill rider 对称位补齐口径)——后置必中附加,独立于掷点与乘区
# (奥术余烬「普攻命中后恒定结算」语义);猎巢人巢控光环(per nest_count)/矿工的执拗开格加成
# (per dig_streak、cap 封顶)同通道合计;档一质变 rider+1 / 档二(crit 时)rider 乘暴击倍率。
@@ -406,7 +406,7 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D
hero_rider += contrib
if hero_rider > 0:
if bool(ctx["crit"]) and bool(inject["rider_tier_crit"]):
hero_rider = int(round(float(hero_rider) * float(db.settlement.damage_roll.crit_multiplier)))
hero_rider = int(round(float(hero_rider) * float(db.combat.settlement.damage_roll.crit_multiplier)))
landed += hero_rider
payload["hero_rider"] = hero_rider
units.apply_damage(target, landed)
@@ -432,7 +432,7 @@ func _kill_overflow(t: Dictionary, target: Dictionary, landed: int) -> void:
# source_class 用中性名(fx_overflow 泛用通道,不挂件名)
var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "fx_overflow", "贯穿弹射",
{"channel": "board",
"base_value": maxi(int(db.settlement.damage_roll.damage_floor), int(landed * int(h.get("value", 50)) / 100)),
"base_value": maxi(int(db.combat.settlement.damage_roll.damage_floor), int(landed * int(h.get("value", 50)) / 100)),
"target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": int(t.round_index),
"origin_cell": t.origin_cell, "attack_type": "pierce"})
if policy == POLICY_SEGMENT:
@@ -460,8 +460,8 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
# 掷点算术归 _enemy_roll;damage_taken 施加面归 _enemy_shield_apply(发射在本函数——
# 与减伤落快照的先后序固定)。
# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 _emit_hero_damage_taken(发射在本函数——
# 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。
func _resolve_enemy_skill(t: Dictionary) -> void:
var hero: Dictionary[Variant, Variant] = units.hero_unit()
var roll := _enemy_roll(t)
@@ -469,9 +469,8 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
# damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers),
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供)
var dt_hits := _fx_hits("damage_taken", {"kind": "enemy_skill", "amount": total,
"source": String(t.context.get("skill_id", "")), "round": int(t.round_index)})
total = int(_enemy_shield_apply(dt_hits, total)["total"])
var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
total = int(dt_res["total"])
units.apply_damage(hero, total)
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
"roll": int(roll["roll"]), "ac": int(roll["threshold"]), "tier": int(roll["discount"]), "attack_type": String(t.attack_type)}
@@ -492,19 +491,32 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
func _enemy_roll(t: Dictionary) -> Dictionary:
var hero: Dictionary[Variant, Variant] = units.hero_unit()
var roll_read: Dictionary[Variant, Variant] = _armor_roll()
var threshold: int = defender_ac(hero) + int(db.settlement.damage_roll.attack_bonus)
var threshold: int = defender_ac(hero) + int(db.combat.settlement.damage_roll.attack_bonus)
var crit: bool = bool(roll_read.crit)
var discount: int = _roll_discount(int(roll_read.roll), threshold)
var cdelta: int = _counter_delta(String(t.attack_type), String(hero.get("armor_type", "light")))
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.damage_roll.crit_multiplier), cdelta)
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.combat.settlement.damage_roll.crit_multiplier), cdelta)
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output"))
var total: int = maxi(int(db.settlement.damage_roll.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
var total: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
return {"roll": int(roll_read.roll), "threshold": threshold, "crit": crit, "discount": discount, "total": total}
# damage_taken 施加面(shield 加法族合计;damage_reduce 逐条减量〔背水〕;ac_bonus 转编排器单点
# 〔石肤 AC 累积归 set_hero_ac_override 消费面〕;counter_add 施加)。返回 {total, shield}。
func _enemy_shield_apply(hits: Array, total: int) -> Dictionary:
# 全局受伤发射单点(迭代 46 卡A T2——所有主角承伤路径唯一发射口:敌方攻击/地形/危险格/错误封印
# 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
# 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。
# payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。
func _emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary:
var hu: Dictionary[Variant, Variant] = units.hero_unit()
var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source,
"cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))}
var hits := _fx_hits("damage_taken", payload)
return {"total": int(_apply_hero_damage_effects(hits, amount)["total"]), "hits": hits}
# damage_taken 唯一消费族(组合次序冻结:shield 加法族合计→damage_reduce 逐条减量〔背水/石层缓冲〕→
# ac_bonus 转编排器单点〔石肤 AC 累积归 set_hero_ac_override 消费面〕→counter_add 施加〔保险丝/石肤/
# 换图系临时计数夹具〕。返回 {total, shield})。
func _apply_hero_damage_effects(hits: Array, total: int) -> Dictionary:
var shield := 0
for h in hits:
match String(h.get("kind", "")):
@@ -520,7 +532,7 @@ func _enemy_shield_apply(hits: Array, total: int) -> Dictionary:
var set_v = h.get("set", 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)
if shield > 0:
total = maxi(int(db.settlement.damage_roll.damage_floor), total - shield)
total = maxi(int(db.combat.settlement.damage_roll.damage_floor), total - shield)
return {"total": total, "shield": shield}
@@ -541,7 +553,7 @@ func _resolve_board_typed(t: Dictionary) -> void:
# 无模式 delta 才入棋盘窗(mode 路由:clamp_bonus=主角攻击面专享、discount_shift=折扣档面)
if String(h.get("kind", "")) == "modify_ticket" and h.has("delta") and String(h.get("mode", "")) == "":
cdelta += int(h.delta)
var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.settlement.damage_roll.damage_floor))
var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.combat.settlement.damage_roll.damage_floor))
units.apply_damage(target, dealt)
emit_row_cb.call({"type": "damage", "source_class": String(t.source_class), "origin_cell": t.origin_cell, "highlight": String(t.highlight)}, int(t.round_index),
"%s → %s" % [String(t.display), String(target.display_name)], dealt,
@@ -556,7 +568,7 @@ func _resolve_board_blast(t: Dictionary) -> void:
_ineffective_row(t, "无存活敌人")
_outcomes[int(t.ticket_id)] = "zero"
return
var blast_dmg: int = maxi(int(db.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
var blast_dmg: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
for u in alive:
units.apply_damage(u, blast_dmg) # 恒定表值(下限保底),逐个;受击即死——死亡即不动手
emit_row_cb.call({"type": "damage", "source_class": String(t.source_class), "origin_cell": t.origin_cell, "highlight": String(t.highlight)}, int(t.round_index),
@@ -565,9 +577,15 @@ func _resolve_board_blast(t: Dictionary) -> void:
_outcomes[int(t.ticket_id)] = "redeemed"
# 棋盘主角直伤(反噬/治疗包负向/地形变异):无类型必中,下限保底;context 键并入行载荷
# 棋盘主角直伤(反噬/治疗包负向/危险格/错误封印资源直伤):无类型必中,下限保底;context 键并入行载荷。
# damage_taken 全局发射点(迭代 46 卡A T2——单据未标 hero_damage_consumed 时在下限前发射并消费一次;
# 地形路径已在授予段开单点发射并标记,此处跳过防重复)
func _resolve_board_flat_hero(t: Dictionary) -> void:
var dealt: int = maxi(int(db.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
var dealt: int = int(t.base_value)
if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)):
var res_flat := _emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index))
dealt = int(res_flat["total"])
dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限)
units.apply_damage(units.hero_unit(), dealt)
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
for k in t.context.keys():
@@ -580,16 +598,16 @@ func _resolve_board_flat_hero(t: Dictionary) -> void:
# 护甲等级掷点(armor_roll 封闭流——逐次掷逐次取号、逐位可回放):返回 {roll, crit}
func _armor_roll() -> Dictionary:
var stream: RandomNumberGenerator = rng.open_stream(String(db.settlement.damage_roll.armor_roll_stream))
var roll: int = stream.randi_range(1, int(db.settlement.damage_roll.roll_faces))
return {"roll": roll, "crit": roll == int(db.settlement.damage_roll.crit_roll)}
var stream: RandomNumberGenerator = rng.open_stream(String(db.combat.settlement.damage_roll.armor_roll_stream))
var roll: int = stream.randi_range(1, int(db.combat.settlement.damage_roll.roll_faces))
return {"roll": roll, "crit": roll == int(db.combat.settlement.damage_roll.crit_roll)}
# 掷点折扣读数:nat 最大面恒全额(暴击分支前置),其余查档(缺口 = 阈值 − 掷点)
func _roll_discount(roll: int, threshold: int) -> int:
if roll == int(db.settlement.damage_roll.crit_roll):
if roll == int(db.combat.settlement.damage_roll.crit_roll):
return 100
return DamageMath.roll_discount_percent(roll, threshold, db.settlement.damage_roll.roll_tier_bounds, db.settlement.damage_roll.roll_tier_discounts)
return DamageMath.roll_discount_percent(roll, threshold, db.combat.settlement.damage_roll.roll_tier_bounds, db.combat.settlement.damage_roll.roll_tier_discounts)
# 防守方护甲等级读数(数值轴——与 armor_type 克制属性轴严格分离):敌人 = spawn 期行值快照;
@@ -599,15 +617,15 @@ func defender_ac(unit: Dictionary) -> int:
if bool(unit.get("is_hero", false)):
var total: int = _hero_ac_override
if total < 0:
total = int(db.hero.armor_class)
total = int(db.combat.hero.armor_class)
for eff in effects.query_active("hero"):
var row: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff.effect_id)
if not row.is_empty() and String(row.unit) == "ac":
total += int(eff.effective_value)
return total
var total_e: int = int(unit.get("armor_class", 0))
for eff_e in effects.query_active("enemy"):
var erow_ac: Dictionary[Variant, Variant] = db.status_effect_row(eff_e.effect_id)
var erow_ac: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff_e.effect_id)
if not erow_ac.is_empty() and String(erow_ac.unit) == "ac":
total_e += int(eff_e.effective_value)
return total_e
@@ -618,14 +636,14 @@ func defender_ac(unit: Dictionary) -> int:
func _counter_delta(attack_type: String, armor: String) -> int:
if attack_type == "":
return 0
var tbl: Dictionary[Variant, Variant] = db.settlement.damage_roll.armor_effectiveness
var tbl: Dictionary[Variant, Variant] = db.combat.settlement.damage_roll.armor_effectiveness
var row: Dictionary[Variant, Variant] = tbl.get(attack_type, {})
return int(row.get(armor, 0))
# 折扣档改善(猎弱本能——折扣表向全额方向移 value 档;查不到当前档 = 不动防御口径)
func _discount_shift_up(discount: int, shift: int) -> int:
var tbl: Array[Variant] = db.settlement.damage_roll.roll_tier_discounts
var tbl: Array[Variant] = db.combat.settlement.damage_roll.roll_tier_discounts
var idx := tbl.find(discount)
if idx < 0:
return discount
@@ -636,12 +654,12 @@ func _discount_shift_up(discount: int, shift: int) -> int:
func clamped_pool(pool: String) -> int:
var total := 0
for eff in effects.query_active("hero") + effects.query_active("enemy"):
var row: Dictionary[Variant, Variant] = db.status_effect_row(eff.effect_id)
var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff.effect_id)
if row.is_empty():
continue
if String(db.settlement.core.effect_pool_map.get(eff.effect_id, "")) == pool and String(row.unit) == "percent":
if String(db.combat.settlement.core.effect_pool_map.get(eff.effect_id, "")) == pool and String(row.unit) == "percent":
total += eff.effective_value # 池内加算(L×m 原样——越域不 clamp 由本处收口)
return clampi(total, int(db.settlement.core.pool_clamp_lo), int(db.settlement.core.pool_clamp_hi))
return clampi(total, int(db.combat.settlement.core.pool_clamp_lo), int(db.combat.settlement.core.pool_clamp_hi))
# 攻击行显示名(掷点档位随行文本可读;重档折扣 = "被压制")
+182 -114
View File
@@ -12,11 +12,15 @@ extends RefCounted
const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commit", "clamp_check",
"armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle",
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed",
"board_installed"]
"board_installed",
# 换图系双事件(refresh_request=玩家请求刷新〔省力周转 apply_discount 订阅面——折扣只改门槛
# 与扣费不改拒绝判据〕;board_refreshed=成功刷新事实〔换图系七件订阅主挂点——装新板后才发射〕)
"refresh_request", "board_refreshed"]
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"]
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件+轮内刷新面〔换图系〕)
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll",
"refresh_request", "board_refreshed"]
## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、
@@ -40,7 +44,11 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
"mine_count_preview",
# forgive_seal_miss=完美保险(本场首次封印落空不计入完美
# 封印判定——订阅 seal_missed 的豁免效果,消费点在授予段;feats.tres 首例 domain=battle 行)
"forgive_seal_miss"]
"forgive_seal_miss",
# 换图系两效果(grant_reserve_ap=矿脉储能·付费挖格产 RAP〔消费点在行动经济 dig_paid
# 受理后一次性入池〕;apply_discount=省力周转·刷新成本折扣〔mode=refresh cost_mult 百分比,
# floor 取整——只改门槛与扣费、不改拒绝判据〕)
"grant_reserve_ap", "apply_discount"]
## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
@@ -94,58 +102,76 @@ func _init(db_ref: ConfigDb = null) -> void:
## 在 dispatch 的 round_start 特判内整桶 clear,adventure 桶随宿主字典引用跨战持久——无副本搬运)。
func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void:
_triggers.clear()
if db != null and db.feats != null:
for row in db.feats.rows:
_load_feat_triggers(feat_ids)
_load_attune_triggers(item_ids)
_push_tree_rules(tree_rules)
## 特性订阅装载域(feats 表序→按持有集过滤→行内数组序——装载声明序第一段)
func _load_feat_triggers(feat_ids: Array) -> void:
if db != null and db.crossrun.feats != null:
for row in db.crossrun.feats.rows:
var fid := String(row.get("feat_id", ""))
if not feat_ids.has(fid):
continue
for tr in (row.get("triggers", []) as Array):
_push_trigger("feat", fid, tr, row)
## 同调秘宝订阅装载域(treasure 同调行序→按装备集过滤→行内数组序——装载声明序第二段)
func _load_attune_triggers(item_ids: Array) -> void:
if db != null:
for row in db.treasure_attunement_rows():
for row in db.crossrun.treasure_items.attunement_rows():
var iid := String(row.get("item_id", ""))
if not item_ids.has(iid):
continue
for tr in (row.get("triggers", []) as Array):
_push_trigger("attune", iid, tr, row)
# 树规则合成订阅(六键按 v0.8 三组双态):posture 两键=「每关首轮窗」语义
# 〔battle_index==1 且 round_index==1;battle_index=关卡域战斗序由 enc 携带〕;
# sense 两组=每关限次〔level 桶计数器 lt 上限闸 + 命中即 counter_add 记账双行——同事件双行同
# 预评估态见闸同记,序=装载序确定;寿命随 LevelRunner.level_counters 每关 new 天然按关清零〕;
# economy 两组零订阅行——消费点=GP 掉落乘算与商品计价折价的 tree_rules 成员判定。数值全 provisional 归 M4。
## 树规则合成订阅域(六键按 v0.8 三组双态——装载声明序第三段):posture 两键=「每关首轮窗」语义
## 〔battle_index==1 且 round_index==1;battle_index=关卡域战斗序由 enc 携带〕;
## sense 两组=每关限次〔level 桶计数器 lt 上限闸 + 命中即 counter_add 记账双行——同事件双行同
## 预评估态见闸同记,序=装载序确定;寿命随 LevelRunner.level_counters 每关 new 天然按关清零〕;
## economy 两组零订阅行——消费点=GP 掉落乘算与商品计价折价的 tree_rules 成员判定。数值全 provisional 归 M4。
func _push_tree_rules(tree_rules: Array) -> void:
for key_v in tree_rules:
var rk := String(key_v)
match rk:
"posture_first_strike":
_push_trigger("tree", rk, {"on": "round_start",
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
{"fact": "round_index", "op": "eq", "value": 1}],
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
"posture_bulwark":
_push_trigger("tree", rk, {"on": "damage_taken",
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
{"fact": "round_index", "op": "eq", "value": 1}],
"do": {"kind": "shield", "value": 1}}, {})
"sense_sharp":
# 锋=踩中危险格伤害减半·每关限次——mine_hit 挂点现成(damage_mult 收集→halve_pct,
# 与惊巢反制同路径;伤害在结算战斗段末兑付,探索阶段零改动)
_push_trigger("tree", rk, {"on": "mine_hit",
"when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}],
"do": {"kind": "damage_mult", "value": 50}}, {})
_push_trigger("tree", rk, {"on": "mine_hit",
"when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}],
"do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_sharp_used",
"value": 1}}, {})
"sense_guard":
# 守=标错返还行动点·每关限次(契约 12 失误补偿——seal_missed 由 _judge_seal 去重闸后
# 发射;queue=下一轮开轮兑现,末轮判出无下一轮=合法作废自动成立)
_push_trigger("tree", rk, {"on": "seal_missed",
"when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}],
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
_push_trigger("tree", rk, {"on": "seal_missed",
"when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}],
"do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_guard_used",
"value": 1}}, {})
_push_tree_rule(String(key_v))
## 单树规则合成行 push(六键 match 分派——词表封闭,未知键零订阅零扰动)
func _push_tree_rule(rk: String) -> void:
match rk:
"posture_first_strike":
_push_trigger("tree", rk, {"on": "round_start",
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
{"fact": "round_index", "op": "eq", "value": 1}],
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
"posture_bulwark":
_push_trigger("tree", rk, {"on": "damage_taken",
"when": [{"fact": "battle_index", "op": "eq", "value": 1},
{"fact": "round_index", "op": "eq", "value": 1}],
"do": {"kind": "shield", "value": 1}}, {})
"sense_sharp":
# 锋=踩中危险格伤害减半·每关限次——mine_hit 挂点现成(damage_mult 收集→halve_pct,
# 与惊巢反制同路径;伤害在结算战斗段末兑付,探索阶段零改动)
_push_trigger("tree", rk, {"on": "mine_hit",
"when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}],
"do": {"kind": "damage_mult", "value": 50}}, {})
_push_trigger("tree", rk, {"on": "mine_hit",
"when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}],
"do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_sharp_used",
"value": 1}}, {})
"sense_guard":
# 守=标错返还行动点·每关限次(契约 12 失误补偿——seal_missed 由 _judge_seal 去重闸后
# 发射;queue=下一轮开轮兑现,末轮判出无下一轮=合法作废自动成立)
_push_trigger("tree", rk, {"on": "seal_missed",
"when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}],
"do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {})
_push_trigger("tree", rk, {"on": "seal_missed",
"when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}],
"do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_guard_used",
"value": 1}}, {})
func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary) -> void:
@@ -203,13 +229,14 @@ func dispatch(ctx: EffectContext, event: String, payload: Dictionary, facts: Cal
return hits
## 计数器归属桶解析(前缀规则唯一出处——round_/lvl_/adv_ 显式桶,其余落 battle 缺省桶)。
## 计数器归属桶解析(前缀规则唯一出处——round_/lvl_/adv_ 显式桶,其余落 battle 缺省桶;
## 前缀判定序 round→lvl→adv→battle 缺省逐字)
static func bucket_of(ctx: EffectContext, counter_id: String) -> Dictionary[Variant, Variant]:
if counter_id.begins_with("round_"):
return ctx.round_bucket
elif counter_id.begins_with("lvl_"):
if counter_id.begins_with("lvl_"):
return ctx.level_bucket
elif counter_id.begins_with("adv_"):
if counter_id.begins_with("adv_"):
return ctx.adventure_bucket
return ctx.battle_bucket
@@ -262,41 +289,68 @@ func _cond_ok(ctx: EffectContext, c: Dictionary, payload: Dictionary, facts: Cal
var val = c.get("value", null)
var actual = _fact_value(ctx, fact, c, payload, facts)
match op:
"eq":
return str(actual) == str(val)
"neq":
return str(actual) != str(val)
"gt":
return int(actual) > int(val)
"gte":
return int(actual) >= int(val)
"lt":
return int(actual) < int(val)
"lte":
return int(actual) <= int(val)
"eq", "neq", "gt", "gte", "lt", "lte":
return _cmp_ok(op, actual, val)
"ends_with":
if val is Array:
for v in (val as Array):
if String(actual).ends_with(String(v)):
return true
return false
return String(actual).ends_with(String(val))
return _ends_with_ok(actual, val)
"has":
# 在场判定(fact 查询面落法——白名单覆盖):value 缺省=非空判定
# (terrain_kind fact:伤害型变异集非空=挖开伤害型地形格/邻格有伤害型地形/地形伤种类在场);
# value 给定=数组包含判定(保留泛用面)。
if c.has("value"):
if actual is Array:
return (actual as Array).has(val)
return false
if actual is Array:
return not (actual as Array).is_empty()
return actual != null and String(actual) != "" # null 安全(敌技伤害载荷无 terrain_kind 时事实面回落 null,判「不在场」而非抛错)
return _has_ok(c, actual)
"mod_zero", "mod_eq", "advantageous", "gte_ref", "lt_ref":
return _counter_op_ok(ctx, c, op, actual)
assert(false, "[效果引擎] when 算子出域: %s" % op)
return false
## 比较算子域(eq/neq/gt/gte/lt/lte 六比较——str 逐字与 int 投影语义逐字)
func _cmp_ok(op: String, actual: Variant, val: Variant) -> bool:
var out := false
match op:
"eq":
out = str(actual) == str(val)
"neq":
out = str(actual) != str(val)
"gt":
out = int(actual) > int(val)
"gte":
out = int(actual) >= int(val)
"lt":
out = int(actual) < int(val)
"lte":
out = int(actual) <= int(val)
return out
## 后缀匹配域(ends_with——值数组任一命中即真)
func _ends_with_ok(actual: Variant, val: Variant) -> bool:
if val is Array:
for v in (val as Array):
if String(actual).ends_with(String(v)):
return true
return false
return String(actual).ends_with(String(val))
## 在场判定域(has——fact 查询面落法,白名单覆盖):value 缺省=非空判定
## (terrain_kind fact:伤害型变异集非空=挖开伤害型地形格/邻格有伤害型地形/地形伤种类在场);
## value 给定=数组包含判定(保留泛用面)
func _has_ok(c: Dictionary, actual: Variant) -> bool:
if c.has("value"):
if actual is Array:
return (actual as Array).has(c.get("value"))
return false
if actual is Array:
return not (actual as Array).is_empty()
return actual != null and String(actual) != "" # null 安全(敌技伤害载荷无 terrain_kind 时事实面回落 null,判「不在场」而非抛错)
## 计数器与矩阵算子域(mod_zero/mod_eq 取模、advantageous 克制矩阵、gte_ref/lt_ref 计数器比较——读经 ctx)
func _counter_op_ok(ctx: EffectContext, c: Dictionary, op: String, actual: Variant) -> bool:
match op:
"mod_zero":
var m := int(val)
var m := int(c.get("value", null))
return m > 0 and int(actual) % m == 0
"mod_eq":
var me_m := int(val)
var me_m := int(c.get("value", null))
return me_m > 0 and int(actual) % me_m == int(c.get("ref", 0))
"advantageous":
# 克制矩阵正面判定(matchup = "{attack_type}_vs_{armor}"——矩阵值 > 0 = 克制成立)
@@ -305,7 +359,6 @@ func _cond_ok(ctx: EffectContext, c: Dictionary, payload: Dictionary, facts: Cal
return int(actual) >= counter(ctx, String(c.get("ref", "")))
"lt_ref":
return int(actual) < counter(ctx, String(c.get("ref", "")))
assert(false, "[效果引擎] when 算子出域: %s" % op)
return false
@@ -329,7 +382,7 @@ func _counter_delta_of(matchup: String) -> int:
var parts := matchup.split("_vs_")
if parts.size() != 2:
return 0
var tbl: Dictionary[Variant, Variant] = db.settlement.damage_roll.armor_effectiveness
var tbl: Dictionary[Variant, Variant] = db.combat.settlement.damage_roll.armor_effectiveness
var row: Dictionary[Variant, Variant] = tbl.get(String(parts[0]), {})
return int(row.get(String(parts[1]), 0))
@@ -350,44 +403,59 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
errs.append({"category": "enum", "message": "%s 订阅事件出域(事件词汇封闭): %s" % [rid, on]})
elif MINE_EVENTS.has(on):
mine_covered = true
for c_v in (tr.get("when", []) as Array):
var c: Dictionary[Variant, Variant] = c_v as Dictionary
for k in c.keys():
if not WHEN_KEYS.has(String(k)):
errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]})
var fact := String(c.get("fact", ""))
if not FACTS.has(fact):
errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 %d 项): %s" % [rid, FACTS.size(), fact]})
if not OPS.has(String(c.get("op", "eq"))):
errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]})
var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary
for k in do_d.keys():
if not DO_KEYS.has(String(k)):
errs.append({"category": "zero_flat", "message": "%s do 键出白名单: %s(机制轴禁无条件属性加项)" % [rid, String(k)]})
var kind := String(do_d.get("kind", ""))
if not EFFECT_KINDS.has(kind):
errs.append({"category": "enum", "message": "%s 效果类型出域(解释器闭集): %s" % [rid, kind]})
# 计数器前缀归属合法性(.tres 行路径——写错前缀加载致命;判据单点 counter_id_legal,与 _push_trigger 同源)
if do_d.has("counter_id"):
var cid := String(do_d.counter_id)
if not counter_id_legal(cid):
errs.append({"category": "enum", "message": "%s counter_id 前缀非法(meta_ 出域/引擎不接存档级): %s" % [rid, cid]})
# value_from/delta_from/mult_from/cap_from 引用面:行字段存在性(悬空=加载致命)——
# do 与 when 两侧同面(when value_from=dawn_order effective_rounds 先例)
for vf_k in ["value_from", "delta_from", "mult_from", "cap_from"]:
if do_d.has(vf_k):
var fld := String(do_d.get(vf_k, ""))
if fld != "" and not row.has(fld):
errs.append({"category": "dangling", "message": "%s do.%s 悬空(行无字段 %s)" % [rid, vf_k, fld]})
for c_v in (tr.get("when", []) as Array):
var c_w: Dictionary[Variant, Variant] = c_v as Dictionary
if c_w.has("value_from"):
var wfld := String(c_w.get("value_from", ""))
if wfld != "" and not row.has(wfld):
errs.append({"category": "dangling", "message": "%s when.value_from 悬空(行无字段 %s)" % [rid, wfld]})
_vtr_when_keys(rid, tr, errs)
_vtr_do_keys(rid, tr, row, errs)
return mine_covered
## when 键域校验:键白名单/fact 词表/算子词表(追加序承原位——when 键→fact→op)
static func _vtr_when_keys(rid: String, tr: Dictionary, errs: Array) -> void:
for c_v in (tr.get("when", []) as Array):
var c: Dictionary[Variant, Variant] = c_v as Dictionary
for k in c.keys():
if not WHEN_KEYS.has(String(k)):
errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]})
var fact := String(c.get("fact", ""))
if not FACTS.has(fact):
errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 %d 项): %s" % [rid, FACTS.size(), fact]})
if not OPS.has(String(c.get("op", "eq"))):
errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]})
## do 键域校验:键白名单(零裸 flat 护栏)/效果类型闭集/计数器前缀合法性+value_from 引用悬空域
static func _vtr_do_keys(rid: String, tr: Dictionary, row: Dictionary, errs: Array) -> void:
var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary
for k in do_d.keys():
if not DO_KEYS.has(String(k)):
errs.append({"category": "zero_flat", "message": "%s do 键出白名单: %s(机制轴禁无条件属性加项)" % [rid, String(k)]})
var kind := String(do_d.get("kind", ""))
if not EFFECT_KINDS.has(kind):
errs.append({"category": "enum", "message": "%s 效果类型出域(解释器闭集): %s" % [rid, kind]})
# 计数器前缀归属合法性(.tres 行路径——写错前缀加载致命;判据单点 counter_id_legal,与 _push_trigger 同源)
if do_d.has("counter_id"):
var cid := String(do_d.counter_id)
if not counter_id_legal(cid):
errs.append({"category": "enum", "message": "%s counter_id 前缀非法(meta_ 出域/引擎不接存档级): %s" % [rid, cid]})
_vtr_from_refs(rid, tr, row, errs)
## value_from/delta_from/mult_from/cap_from 引用面:行字段存在性(悬空=加载致命)——
## do 与 when 两侧同面(when value_from=dawn_order effective_rounds 先例)
static func _vtr_from_refs(rid: String, tr: Dictionary, row: Dictionary, errs: Array) -> void:
var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary
for vf_k in ["value_from", "delta_from", "mult_from", "cap_from"]:
if do_d.has(vf_k):
var fld := String(do_d.get(vf_k, ""))
if fld != "" and not row.has(fld):
errs.append({"category": "dangling", "message": "%s do.%s 悬空(行无字段 %s)" % [rid, vf_k, fld]})
for c_v in (tr.get("when", []) as Array):
var c_w: Dictionary[Variant, Variant] = c_v as Dictionary
if c_w.has("value_from"):
var wfld := String(c_w.get("value_from", ""))
if wfld != "" and not row.has(wfld):
errs.append({"category": "dangling", "message": "%s when.value_from 悬空(行无字段 %s)" % [rid, wfld]})
## 组合规则(需求卡片 §3.4 六族——效果类型的必填属性,发射点施加面引用)
## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap(非 transfer)、
+5 -5
View File
@@ -52,12 +52,12 @@ func _normalize_override(raw: Dictionary) -> Dictionary[Variant, Variant]:
# 调试浮窗百分比后乘;pct=0 时乘法恒等——MC 锚零漂移)----
func effective_max_hp() -> int:
var base: int = int(db.hero.max_hp) + int(_base_override.get("max_hp", 0)) + journal.bonus_total("max_hp")
var base: int = int(db.combat.hero.max_hp) + int(_base_override.get("max_hp", 0)) + journal.bonus_total("max_hp")
return maxi(1, int(round(float(base) * (100.0 + float(_override[1])) / 100.0)))
func effective_atk() -> int:
var base: int = int(db.hero.atk) + int(_base_override.get("atk", 0)) + journal.bonus_total("atk")
var base: int = int(db.combat.hero.atk) + int(_base_override.get("atk", 0)) + journal.bonus_total("atk")
return maxi(1, int(round(float(base) * (100.0 + float(_override[0])) / 100.0)))
@@ -70,10 +70,10 @@ func effective_ac() -> int:
# 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线,
# 与 hero 表两轴删除前 armor_class=10 行为连续)
func _armor_base_ac() -> int:
var at: String = armor_type if armor_type != "" else db.forge_default_armor_type()
var row: Dictionary[Variant, Variant] = db.forge_armor_route_by_type(at)
var at: String = armor_type if armor_type != "" else db.crossrun.forge.default_armor_type
var row: Dictionary[Variant, Variant] = db.crossrun.forge.armor_route_by_type(at)
if row.is_empty():
return int(db.hero.armor_class) # 表缺失防御兜底(现役基准)
return int(db.combat.hero.armor_class) # 表缺失防御兜底(现役基准)
return int(row.get("base_ac", 0))
+20 -100
View File
@@ -9,7 +9,7 @@
class_name LevelRunner
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 step_row(row: Dictionary) ## 转发当前 战斗结算 结算行
signal round_started(round_index: int)
@@ -41,7 +41,7 @@ var hero_hp: int:
var gp: int: # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要)
get: return _wallet.gp
set(v): _wallet.gp = v
var _shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
var shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
# 协作对象私有(E1/E3 封装收口):settlement 读取经 current_battle() 成文出口
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(TreasureItems;跨战斗延续,
@@ -96,10 +96,10 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> v
_hero_armor_type = String(ctx.get("hero_armor_type", ""))
_voucher_rap = maxi(0, int(ctx.get("voucher_rap", 0)))
_voucher_time_sand = maxi(0, int(ctx.get("voucher_time_sand", 0)))
_shop = ShopSession.new(db, rng)
shop = ShopSession.new(db, rng)
var pool: Dictionary[Variant, Variant] = ctx.get("pool_wiring", {}) as Dictionary
if String(ctx.get("shop_mode", "")) == "pool" and not pool.is_empty():
_shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
pool.get("ticket", Callable()) as Callable, pool.get("cap", Callable()) as Callable)
_bind_shop()
@@ -302,8 +302,8 @@ func breakpoint_fragment() -> Dictionary[Variant, Variant]:
"node_id": current_node_id(),
"visited_ids": visited_ids(),
"aux_items": treasure_snapshot,
"shop_shelf": _shop.shop_bp_shelf(),
"shop_visit_buys": _shop.shop_bp_visit_buys(),
"shop_shelf": shop.shop_bp_shelf(),
"shop_visit_buys": shop.shop_bp_visit_buys(),
}
@@ -318,7 +318,7 @@ func restore_fragment(resume_state: Dictionary) -> Dictionary:
func _find_level_row(level_index: int) -> Dictionary:
return db.level_row(level_index) # 表访问收口(行集直扫归 LevelTable/ConfigDB 访问器)
return db.run.level.row_by_level_index(level_index) # 表访问收口(行集直扫归 LevelTable/ConfigDB 访问器)
# ---- 图源装配(关卡节点图生成 v1.1——图 = f(参数档, 种子) 纯函数;无模板池、无兜底图)----
@@ -327,7 +327,7 @@ func _find_level_row(level_index: int) -> Dictionary:
# LevelTable.validate_graph(与产出侧自证同源规格、分工不重叠);违规=起关拒绝,不降级不回退。
func _assign_generated_graph(row: Dictionary, seed_val: int) -> bool:
var level_index: int = int(row.get("level_index", 0))
var params: Dictionary[Variant, Variant] = db.level_gen_params_row(level_index)
var params: Dictionary[Variant, Variant] = db.run.level_gen_params.row_by_level_index(level_index)
if params.is_empty():
push_error("[关卡节点地图] level %d 无参数档行(level_gen_params 缺行——配置错误)" % level_index)
return false
@@ -433,7 +433,7 @@ func visited_ids() -> Array:
# ---- 类型可见性读面(信息迷雾 v2.1:近处见类型,远处起雾)----
# 可见集 = Boss/终点(type=="boss")∪ 当前候选(下一跳恒可见——生成承诺)∪
# {id : |层(id) − 锚层| ≤ N};N = db.level.info_visible_layers(禁硬编码);
# {id : |层(id) − 锚层| ≤ N};N = db.run.level.info_visible_layers(禁硬编码);
# 锚层 = 当前节点层,入口停点(无当前节点)= 0;层 = 入口 BFS 深度(与呈现层分层同源口径)。
# 纯函数零状态:已过节点不豁免(单圈语义,零累积记忆);断点快照零新增字段。
func type_visible_ids() -> Array[int]:
@@ -443,7 +443,7 @@ func type_visible_ids() -> Array[int]:
var cur := current_node()
if not cur.is_empty():
anchor = int(layers.get(_node_id(cur), 0))
var n: int = int(db.level.info_visible_layers)
var n: int = int(db.run.level.info_visible_layers)
for node in nodes():
var nid := _node_id(node)
if String(node.get("type", "")) == "boss" \
@@ -503,11 +503,11 @@ func _enter_node(node: Dictionary) -> bool:
"shop":
# 商店房兑付:到达即开店(覆盖层归呈现层),购买出账/
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
if _shop.pool_mode:
_shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
node_entered.emit(node, {"gp": gp, "shelf": true})
return true
node_entered.emit(node, {"gp": gp}) # legacy 固定三件(off 对照承载——零 RNG 零池消费)
var payload := {"gp": gp} # legacy 固定三件(off 对照承载——零 RNG 零池消费)
if shop.pool_mode:
shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
payload = {"gp": gp, "shelf": true}
node_entered.emit(node, payload)
return true
push_error("[关卡节点地图] 未知节点类型: %s" % str(node))
return false
@@ -517,7 +517,7 @@ func _start_battle(node: Dictionary) -> bool:
_settlement = BattleOrchestrator.new(db, rng)
if treasure_inventory == null:
treasure_inventory = TreasureItems.new(db) # 惰性建——关卡域单实例(跨战斗延续)
_settlement.treasure_inventory = treasure_inventory # 库存注入(授予路由+use_treasure 编排消费面)
_settlement.reward_grant().treasure_inventory = self.treasure_inventory # 库存注入(经授予件出口直注——注入时序在宿主构造之后不变)
if _settlement != null:
_settlement.set_treasure_unlocked(_treasure_unlocked) # 解锁门控下传(装板摇号前)
# core↔core 接线只许方法 Callable(静态规则 S6——lambda 捕获 self 会延长
@@ -701,7 +701,7 @@ func continue_after_node() -> bool:
if t != "reward" and t != "rest" and t != "shop":
return false
if t == "shop":
_shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
return _present_choices()
@@ -721,12 +721,12 @@ func close_wallet() -> void:
# ---- 商店房兑付(GP+商店房实施批:呈现层只读投影 + 购买出账)----
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);本类保留同签名委托与件注入/
# 白盒入口——外部调用面与断点取数面零变化。
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);外部调用面=调用方经宿主公开
# 成员 shop 直连(调用直连不留委托壳——2026-10-26 Middle Man 裁定)。
# 件注入(建件与宿主引用换代时调用——钱包/主角账三件/图行/装配集为共享引用,出账直接落同一引用)
func _bind_shop() -> void:
_shop.bind({
shop.bind({
"wallet": _wallet,
"journal": _journal,
"level_row": level_row,
@@ -746,83 +746,3 @@ func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/关卡完成回血同式)
func _gain_max_hp_flat(flat: int) -> void:
_stats.hero_hp = mini(_stats.hero_hp + flat, _stats.effective_max_hp())
func shop_products() -> Array:
return _shop.shop_products()
func current_price(prow: Dictionary) -> int:
return _shop.current_price(prow)
func purchased_tier(product_id: String) -> int:
return _shop.purchased_tier(product_id)
func buy_product(product_id: String) -> bool:
return _shop.buy_product(product_id)
func player_request_refresh() -> bool:
return _shop.player_request_refresh()
# 散折价单点(测试取数面沿用本类调用名)
func _spend_discounted(price: int) -> int:
return _shop._spend_discounted(price)
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
# 池消费 handler + 专长凭证判定 + cap 检查回调(cap 满 kind 剔出货架候选视图)。
func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable(), cap_check: Callable = Callable()) -> void:
_shop.set_shop_pool(deck_ref, consume_handler, ticket_consider, cap_check)
# 货架抽签白盒入口(测试取数面沿用本类调用名)
func _shop_draw_shelf() -> void:
_shop._draw_shelf()
func pool_stock(kind: String) -> int:
return _shop.pool_stock(kind)
func pool_price(kind: String) -> int:
return _shop.pool_price(kind)
func shop_shelf() -> Array:
return _shop.shop_shelf()
func shop_refresh_free_left() -> int:
return _shop.shop_refresh_free_left()
func shop_refresh_cost() -> int:
return _shop.shop_refresh_cost()
func shop_refresh() -> bool:
return _shop.shop_refresh()
func spend_gp(amount: int) -> bool:
return _shop.spend_gp(amount)
func buy_pool_item(kind: String) -> bool:
return _shop.buy_pool_item(kind)
func is_shop_pool() -> bool:
return _shop.is_shop_pool()
func shop_bp_shelf() -> Array:
return _shop.shop_bp_shelf()
func shop_bp_visit_buys() -> int:
return _shop.shop_bp_visit_buys()
+159 -170
View File
@@ -7,6 +7,7 @@ extends Node
var db: ConfigDb
var save: SaveManagerCore
var meta_state: MetaStateStore # 存档内元进度数据读写唯一层(K3′ 装配成员——架构 :90 直连)
# MetaProgression 是 autoload(仿 RngService 先例:类名加 Core 后缀、
@@ -16,6 +17,8 @@ var save: SaveManagerCore
func _init(db_ref: ConfigDb = null, save_ref: SaveManagerCore = null) -> void:
db = db_ref
save = save_ref
if save != null:
meta_state = save.meta_state
func _ready() -> void:
@@ -23,31 +26,23 @@ func _ready() -> void:
db = get_node("/root/ConfigDB")
if save == null:
save = get_node("/root/SaveManager")
func keystone_balance() -> int:
return save.keystone_balance()
meta_state = save.meta_state
# ---- 秘宝解锁服务(解锁混合制,契约 6)----
# 解锁条件单一事实源=treasure_items.tres 行内 unlock_keystone_cost/unlock_requires 谓词
# (C1 逐件分配锁定表)。
# 道具解锁读数访问器(组合根/呈现层解锁门控查询面——禁再穿透 meta.save 直读 save_manager)
func is_item_unlocked(item_id: String) -> bool:
return save.item_unlocked(item_id)
# (C1 逐件分配锁定表)。钥石余额/道具解锁读数消费方直连 meta.meta_state(纯转发读已随件退役)。
# 解锁条件计数读数单点(谓词 key → 生涯计数——封闭枚举=TreasureItemTable.UNLOCK_COUNTER_KEYS;
# 词表加键 = 此处增一 match 分支,阈值比较与解锁写档零改动)
func _treasure_unlock_progress(key: String) -> int:
match key:
"adventure_cleared":
return save.adventure_cleared()
return meta_state.career_state().adventure_cleared
"boss_kill_count":
return save.boss_kill_count()
return meta_state.career_state().boss_kill_count
"vision_used":
return save.vision_used()
return meta_state.career_state().vision_used
_:
return 0 # 未知计数键恒 0 = 永不解锁(fail-safe 非致命——schema 词表校验兜底,不静默误解锁)
@@ -55,15 +50,15 @@ func _treasure_unlock_progress(key: String) -> int:
# 钥石购买解锁(营地秘宝匣唯一购买写口):仅购买件(行带 unlock_keystone_cost)可购;
# 已解锁/余额不足/条件件误购 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币)。
func buy_treasure_unlock(item_id: String) -> bool:
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(item_id)
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(item_id)
var price := int(arow.get("unlock_keystone_cost", 0))
if arow.is_empty() or price < 1 or (arow.get("unlock_requires", []) as Array).size() > 0:
return false # 非购买件(条件件/无价行)拒绝
if save.item_unlocked(item_id):
if meta_state.item_unlocked(item_id):
return false # 已解锁重复购买拒绝
if not save.spend_keystone(price):
if not meta_state.spend_keystone(price):
return false # 余额不足(零副作用)
save.unlock_item(item_id)
meta_state.unlock_item(item_id)
return save.write_to_disk()
@@ -71,9 +66,9 @@ func buy_treasure_unlock(item_id: String) -> bool:
# 全部谓词达标才解锁(多谓词=AND——天眼双条件);幂等:已解锁跳过,重复刷新零重复入账。
func refresh_treasure_unlocks() -> Array:
var new_unlocks: Array[String] = []
for row in db.treasure_attunement_rows():
for row in db.crossrun.treasure_items.attunement_rows():
var iid := String(row.get("item_id", ""))
if save.item_unlocked(iid):
if meta_state.item_unlocked(iid):
continue
var ureqs: Array[Variant] = row.get("unlock_requires", []) as Array
if ureqs.is_empty():
@@ -85,7 +80,7 @@ func refresh_treasure_unlocks() -> Array:
met = false
break
if met:
save.unlock_item(iid)
meta_state.unlock_item(iid)
new_unlocks.append(iid)
if not new_unlocks.is_empty():
save.write_to_disk()
@@ -96,9 +91,9 @@ func refresh_treasure_unlocks() -> Array:
# {item_id, display_name, desc, tier, unlocked, mode(keystone|condition|owned), price, reqs:[{key,threshold,progress}]}
func treasure_shop_status() -> Array:
var out: Array[Dictionary] = []
for row in db.treasure_attunement_rows():
for row in db.crossrun.treasure_items.attunement_rows():
var iid := String(row.get("item_id", ""))
var unlocked := save.item_unlocked(iid)
var unlocked := meta_state.item_unlocked(iid)
var ureqs: Array[Variant] = row.get("unlock_requires", []) as Array
var req_view: Array[Variant] = []
for req_v in ureqs:
@@ -128,18 +123,18 @@ func treasure_shop_status() -> Array:
# 等级语义:未解锁/未锻造=0 加值(裸体态);锻造第一级=「获得装备」=首个 +1;逐级递进。
# 路线解锁态派生(不落档——初始持有∨直购∨练满前一档;派生单点只此一处)
func forge_route_unlocked(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
func _forge_route_unlocked(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.crossrun.forge.route(route_id)
if row.is_empty():
return false
if bool(row.get("initial_owned", false)):
return true
if save.forge_unlocked_purchases().has(route_id):
if meta_state.forge_state().unlocked.has(route_id):
return true
var prereq := String(row.get("prereq_route", ""))
if prereq != "":
var prow: Dictionary[Variant, Variant] = db.forge_route(prereq)
if not prow.is_empty() and save.forge_route_level(prereq) >= int(prow.get("cap", 0)):
var prow: Dictionary[Variant, Variant] = db.crossrun.forge.route(prereq)
if not prow.is_empty() and meta_state.forge_state().levels.get(prereq, 0) >= int(prow.get("cap", 0)):
return true
return false
@@ -147,86 +142,60 @@ func forge_route_unlocked(route_id: String) -> bool:
# 路线购买(几何价=基础价×150%^已购级数——锚表 20/30/45/67/101;等级+1 即时写盘)。
# 未解锁/已满级/余额不足/未知路线 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币不写盘)。
func forge_buy_level(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
if row.is_empty() or not forge_route_unlocked(route_id):
var row: Dictionary[Variant, Variant] = db.crossrun.forge.route(route_id)
if row.is_empty() or not _forge_route_unlocked(route_id):
return false
var lvl := save.forge_route_level(route_id)
var lvl: int = meta_state.forge_state().levels.get(route_id, 0)
if lvl >= int(row.get("cap", 0)):
return false
var price := ForgeTable.level_price(row, lvl)
if not save.spend_keystone(price):
if not meta_state.spend_keystone(price):
return false
save.set_forge_route_level(route_id, lvl + 1)
meta_state.set_forge_route_level(route_id, lvl + 1)
return save.write_to_disk()
# 一次性解锁价直购(并联双通道②)。已解锁(含练满前一档派生)= 拒绝零收费——两通道互不影响:
# 未练满也可直购;直购后练满不重复收(练满通道本就是派生、零收费)。初始持有行无解锁面。
func forge_buy_unlock(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
var row: Dictionary[Variant, Variant] = db.crossrun.forge.route(route_id)
if row.is_empty() or bool(row.get("initial_owned", false)):
return false
if forge_route_unlocked(route_id):
if _forge_route_unlocked(route_id):
return false
var price := int(row.get("unlock_price", 0))
if price < 1 or not save.spend_keystone(price):
if price < 1 or not meta_state.spend_keystone(price):
return false
save.add_forge_unlock(route_id)
meta_state.add_forge_unlock(route_id)
return save.write_to_disk()
# 当前武器/甲选中切换(免费——只受理已解锁路线;写存档域+即时写盘。冒险开始锁死归参数面快照,
# 契约 8;冒险中无切换入口=UI 面无路径可达本口)
func forge_select_weapon(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(route_id):
# 契约 8;冒险中无切换入口=UI 面无路径可达本口)。并组入口:kind ∈ {weapon, armor}。
func forge_select(kind: String, route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.crossrun.forge.route(route_id)
if row.is_empty() or String(row.get("kind", "")) != kind or not _forge_route_unlocked(route_id):
return false
save.set_forge_weapon(route_id)
if kind == "weapon":
meta_state.set_forge_weapon(route_id)
else:
meta_state.set_forge_armor(route_id)
return save.write_to_disk()
func forge_select_armor(route_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.forge_route(route_id)
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(route_id):
return false
save.set_forge_armor(route_id)
return save.write_to_disk()
# 当前选中武器攻击加值 / 当前甲 AC 加值(路线等级×每级增量——0=裸体态/未锻造合法态)。
# 选中态异常(未解锁/表缺行)防御回退 0——注入面只认合法态,禁把脏选中带进冒险。
func forge_weapon_atk_bonus() -> int:
var rid := save.forge_weapon()
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid):
return 0
return save.forge_route_level(rid) * int(row.get("per_level_gain", 0))
func forge_armor_ac_bonus() -> int:
var rid := save.forge_armor()
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid):
return 0
return save.forge_route_level(rid) * int(row.get("per_level_gain", 0))
# 两根类型轴当前生效值(选中路线类型轴;选中态异常/未解锁=锻造配置默认值——契约 2 回退面)。
# 两根类型轴当前生效值({attack, armor} 并组承载;选中态异常/未解锁=锻造配置默认值——契约 2 回退面)。
# 默认值与柜台解锁状态解耦:树钥匙未点亮时本函数照常返回默认值(配置常驻可读)。
func hero_attack_type() -> String:
var rid := save.forge_weapon()
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid):
return db.forge_default_attack_type()
return String(row.get("type_axis", ""))
func hero_armor_type() -> String:
var rid := save.forge_armor()
var row: Dictionary[Variant, Variant] = db.forge_route(rid)
if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid):
return db.forge_default_armor_type()
return String(row.get("type_axis", ""))
func hero_type_axes() -> Dictionary:
var fs := meta_state.forge_state()
var wrow: Dictionary[Variant, Variant] = db.crossrun.forge.route(String(fs["weapon"]))
var attack := db.crossrun.forge.default_attack_type
if not wrow.is_empty() and String(wrow.get("kind", "")) == "weapon" and _forge_route_unlocked(String(fs["weapon"])):
attack = String(wrow.get("type_axis", ""))
var arow: Dictionary[Variant, Variant] = db.crossrun.forge.route(String(fs["armor"]))
var armor := db.crossrun.forge.default_armor_type
if not arow.is_empty() and String(arow.get("kind", "")) == "armor" and _forge_route_unlocked(String(fs["armor"])):
armor = String(arow.get("type_axis", ""))
return {"attack": attack, "armor": armor}
# ---- 锻造工坊柜台只读投影(呈现层数据源:两区六路线行板;余额联动禁用归视图)----
@@ -235,16 +204,16 @@ func hero_armor_type() -> String:
# prereq_level, prereq_cap, prereq_maxed}——纯结构面,禁嵌余额判断(视图按 balance 禁用,同秘宝匣先例)。
func forge_shop_status() -> Array:
var out: Array[Dictionary] = []
for row in db.forge_routes():
for row in db.crossrun.forge.route_rows():
var rid := String(row.get("route_id", ""))
var lvl := save.forge_route_level(rid)
var lvl: int = meta_state.forge_state().levels.get(rid, 0)
var cap_v := int(row.get("cap", 0))
var unlocked := forge_route_unlocked(rid)
var unlocked := _forge_route_unlocked(rid)
var prereq := String(row.get("prereq_route", ""))
var plvl := save.forge_route_level(prereq) if prereq != "" else 0
var pcap := int(db.forge_route(prereq).get("cap", 0)) if prereq != "" else 0
var selected := (String(row.get("kind", "")) == "weapon" and save.forge_weapon() == rid) \
or (String(row.get("kind", "")) == "armor" and save.forge_armor() == rid)
var plvl: int = meta_state.forge_state().levels.get(prereq, 0) if prereq != "" else 0
var pcap := int(db.crossrun.forge.route(prereq).get("cap", 0)) if prereq != "" else 0
var selected: bool = (String(row.get("kind", "")) == "weapon" and meta_state.forge_state().weapon == rid) \
or (String(row.get("kind", "")) == "armor" and meta_state.forge_state().armor == rid)
out.append({
"route_id": rid,
"display_name": String(row.get("display_name", "")),
@@ -258,7 +227,7 @@ func forge_shop_status() -> Array:
"next_price": ForgeTable.level_price(row, lvl) if (unlocked and lvl < cap_v) else 0,
"unlock_price": int(row.get("unlock_price", 0)),
"prereq_route": prereq,
"prereq_name": String(db.forge_route(prereq).get("display_name", "")) if prereq != "" else "",
"prereq_name": String(db.crossrun.forge.route(prereq).get("display_name", "")) if prereq != "" else "",
"prereq_level": plvl,
"prereq_cap": pcap,
"prereq_maxed": prereq != "" and plvl >= pcap,
@@ -274,10 +243,10 @@ func forge_shop_status() -> Array:
# apply_adventure_summary 逐关卡完成关次置位)。本函数只做标记+入账+归档。
func settle_adventure(summary: Dictionary) -> Dictionary:
# 首次冒险布尔位置位(关卡节点图生成 v0.4——完成/失败/放弃三结局同经本收束挂点,写入只在收束)
save.mark_first_adventure_done()
meta_state.mark_first_adventure_done()
var drops: Array[Variant] = (summary.get("keystone_drops", []) as Array).duplicate(true)
save.apply_adventure_summary(summary, drops)
save.add_vision_used(int(summary.get("vision_used_delta", 0))) # 真视使用计数收束聚合入档(信标网络行为条件)
meta_state.apply_adventure_summary(summary, drops)
meta_state.add_vision_used(int(summary.get("vision_used_delta", 0))) # 真视使用计数收束聚合入档(信标网络行为条件)
var new_unlocks := refresh_treasure_unlocks() # 条件解锁刷新
save.write_to_disk()
var total := int(summary.get("keystone_total", 0))
@@ -299,51 +268,75 @@ func settle_adventure(summary: Dictionary) -> Dictionary:
# 全零 = 白板合法态(无解锁不得改变任何局内规则);override 禁写表(表运行期只读)。
func hero_override() -> Dictionary:
# 锻造路线加值内联(原 forge_weapon_atk_bonus/forge_armor_ac_bonus 单调用点收编——输出逐字:
# 路线等级×每级增量,选中态异常/未解锁防御回退 0)
var fs := meta_state.forge_state()
var atk_bonus := _forge_axis_bonus(fs, "weapon", "weapon")
var ac_bonus := _forge_axis_bonus(fs, "armor", "armor")
# 树淬炼 flat 内联(原 tree_stat_flat 单调用点收编——四线 flat 汇总同轮扫描,三轴输出逐字等价)
var tree_atk := 0
var tree_hp := 0
var tree_ac := 0
var nodes: Dictionary[Variant, Variant] = meta_state.tree_state()["nodes"]
for row in db.crossrun.meta_tree.rows:
var lvl := int(nodes.get(String(row.get("node_id", "")), 0))
if lvl <= 0:
continue
var fx: Dictionary[Variant, Variant] = row.get("effects", {}) as Dictionary
tree_atk += int(fx.get("atk", 0)) * lvl
tree_hp += int(fx.get("max_hp", 0)) * lvl
tree_ac += int(fx.get("armor_class", 0)) * lvl
return {
"atk": forge_weapon_atk_bonus() + tree_stat_flat("atk"),
"max_hp": tree_stat_flat("max_hp"),
"armor_class": forge_armor_ac_bonus() + tree_stat_flat("armor_class"),
"atk": atk_bonus + tree_atk,
"max_hp": tree_hp,
"armor_class": ac_bonus + tree_ac,
}
# 锻造轴加值原语(选中路线等级×每级增量;选中态异常(未解锁/表缺行)防御回退 0——注入面只认合法态)
func _forge_axis_bonus(fs: Dictionary, sel_key: String, kind: String) -> int:
var rid := String(fs[sel_key])
var row: Dictionary[Variant, Variant] = db.crossrun.forge.route(rid)
if row.is_empty() or String(row.get("kind", "")) != kind or not _forge_route_unlocked(rid):
return 0
return int(fs["levels"].get(rid, 0)) * int(row.get("per_level_gain", 0))
# ---- 天赋树段(设计权威 整合定稿 §1.4 一树双币三脉一脊)----
# 全树已购总等级(row-gating 输入——「投入点数累计解锁新行」)
func tree_spent_total() -> int:
func _tree_spent_total() -> int:
var total := 0
for lvl in (save.tree_nodes() as Dictionary).values():
for lvl in (meta_state.tree_state().nodes as Dictionary).values():
total += int(lvl)
return total
# 节点可购校验(行门槛 + 显式前置 + 币 + cap;free 节点=钥匙不经购买,彩蛋=觉醒不经购买)
func tree_can_buy(node_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
if row.is_empty():
return false
var row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(node_id)
# 彩蛋不经购买(觉醒唯一入口=_check_easter_egg 特化判定——V8 反例:手动购亮=红)
if String(row.get("kind", "")) == "easter":
return false
var lvl := save.tree_level(node_id)
if lvl >= int(row.get("cap", 1)):
if row.is_empty() or String(row.get("kind", "")) == "easter":
return false
var nodes: Dictionary[Variant, Variant] = meta_state.tree_state()["nodes"]
var lvl := int(nodes.get(node_id, 0))
var blocked := lvl >= int(row.get("cap", 1))
# 刷新升级节点:cap 主语=Y 账本(单一事实源——树上入口与装配段同 cap 同价,防双账漂移)
if node_id == "ks_refresh" and db.meta_attune_refresh_row(save.attune_refresh_level() + 1).is_empty():
return false
if node_id == "ks_refresh" and db.crossrun.meta.attune_refresh_row(meta_state.attune_state().refresh_level + 1).is_empty():
blocked = true
# 行门槛(一线一节点:脊柱节点第 N 级需全树总投入 ≥4×(N−1))
if String(row.get("vein", "")) == "spine" and String(row.get("kind", "")) == "stat":
if tree_spent_total() < db.meta_tree.level_gate(lvl + 1):
return false
if String(row.get("vein", "")) == "spine" and String(row.get("kind", "")) == "stat" and _tree_spent_total() < db.crossrun.meta_tree.level_gate(lvl + 1):
blocked = true
for req in (row.get("requires", []) as Array):
if save.tree_level(String(req)) < 1:
return false
if int(nodes.get(String(req), 0)) < 1:
blocked = true
# 等级阈值前置(requires_level {node_id: n}:前置节点等级 ≥ n 才可购;
# 现役表零配置=空转零行为影响)
var req_lv: Dictionary[Variant, Variant] = row.get("requires_level", {}) as Dictionary
for req_id in req_lv.keys():
if save.tree_level(String(req_id)) < int(req_lv[req_id]):
return false
return true
if int(nodes.get(String(req_id), 0)) < int(req_lv[req_id]):
blocked = true
return not blocked
# 节点购买(营地树 UI 唯一写口):树全钥石单币。free=工作间钥匙零币点亮
@@ -353,13 +346,13 @@ func tree_can_buy(node_id: String) -> bool:
func tree_buy(node_id: String) -> bool:
if node_id == "ks_refresh":
return buy_attune_refresh()
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
var row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(node_id)
if row.is_empty() or not tree_can_buy(node_id):
return false
var price := int(row.get("price", 0))
if not save.spend_keystone(price):
if not meta_state.spend_keystone(price):
return false
save.set_tree_level(node_id, save.tree_level(node_id) + 1)
meta_state.set_tree_level(node_id, meta_state.tree_state().nodes.get(node_id, 0) + 1)
_check_easter_egg()
return save.write_to_disk()
@@ -367,45 +360,35 @@ func tree_buy(node_id: String) -> bool:
# Fork 切换(营地树 UI——一节点双态:购买解锁组默认 A 态,换态走本口零币;同态重复=零操作;
# 未解锁组切换=拒绝——解锁唯一入口=购买面 tree_buy_fork)
func tree_toggle_fork(group: String, side: String) -> bool:
if not db.meta_tree.FORK_GROUPS.has(group) or not (side == "a" or side == "b"):
if not db.crossrun.meta_tree.FORK_GROUPS.has(group) or not (side == "a" or side == "b"):
return false
var cur := save.fork_side(group)
var cur: String = meta_state.tree_state().fork_sides.get(group, "")
if cur == "" or cur == side:
return false # 未解锁走购买面;同态零操作
save.set_fork_side(group, side)
meta_state.set_fork_side(group, side)
return save.write_to_disk()
# Fork 节点购买入口(一节点双态——购=解锁开关并置默认 A 态;重复购买由 cap 经 can_buy 拦截)
func tree_buy_fork(node_id: String) -> bool:
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
var row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(node_id)
if row.is_empty() or String(row.get("kind", "")) != "fork":
return tree_buy(node_id)
if not tree_can_buy(node_id):
return false
var price := int(row.get("price", 0))
if not save.spend_keystone(price):
if not meta_state.spend_keystone(price):
return false
save.set_tree_level(node_id, 1)
save.set_fork_side(String(row.get("fork_group", "")), "a") # 解锁默认 A 态
meta_state.set_tree_level(node_id, 1)
meta_state.set_fork_side(String(row.get("fork_group", "")), "a") # 解锁默认 A 态
return save.write_to_disk()
# 树效果投影:四线 flat 汇总 + rule 键集(激活 Fork 侧 effects 并入)
func tree_stat_flat(key: String) -> int:
var total := 0
for row in db.tree_rows():
var lvl := save.tree_level(String(row.get("node_id", "")))
if lvl <= 0:
continue
total += int((row.get("effects", {}) as Dictionary).get(key, 0)) * lvl
return total
# 树规则键投影(激活 Fork 侧 effects 并入)
func tree_rule_keys() -> Array:
var out: Array[Variant] = []
var sides: Dictionary[Variant, Variant] = save.fork_sides()
for row in db.tree_rows():
var sides: Dictionary[Variant, Variant] = meta_state.tree_state().fork_sides
for row in db.crossrun.meta_tree.rows:
if String(row.get("kind", "")) != "fork":
continue
var active := String(sides.get(String(row.get("fork_group", "")), ""))
@@ -418,10 +401,15 @@ func tree_rule_keys() -> Array:
return out
# 营地树面投影(CampOverlay _rebuild_tree 数据源——行板只读;账面归本类、拼句归视图同款分工)
func tree_camp_rows() -> Array:
# 营地树面投影(并组入口——{rows, fork_groups};CampOverlay _rebuild_tree 数据源——行板只读;账面归本类、拼句归视图同款分工)
func tree_camp_status() -> Dictionary:
return {"rows": _tree_camp_rows(), "fork_groups": _tree_fork_groups()}
## 树行板投影段(原 tree_camp_rows)
func _tree_camp_rows() -> Array:
var out: Array[Variant] = []
for row in db.tree_rows():
for row in db.crossrun.meta_tree.rows:
if String(row.get("kind", "")) == "fork":
continue # Fork 行由 fork_groups 段承载
out.append({
@@ -430,32 +418,33 @@ func tree_camp_rows() -> Array:
"vein": String(row.get("vein", "")),
"kind": String(row.get("kind", "")),
"cap": int(row.get("cap", 1)),
"level": save.attune_refresh_level() if String(row.get("node_id", "")) == "ks_refresh" else save.tree_level(String(row.get("node_id", ""))), # 刷新升级行主语=Y 账本(同账路由 V15)
"level": meta_state.attune_state().refresh_level if String(row.get("node_id", "")) == "ks_refresh" else meta_state.tree_state().nodes.get(String(row.get("node_id", "")), 0), # 刷新升级行主语=Y 账本(同账路由 V15)
"price": int(row.get("price", 0)),
"currency": String(row.get("currency", "keystone")),
"can_buy": tree_can_buy(String(row.get("node_id", ""))),
"gate_text": tree_gate_text(String(row.get("node_id", ""))),
"gate_text": _tree_gate_text(String(row.get("node_id", ""))),
})
return out
# 行板门槛文案(R1 机制位呈现——requires_level 未达标项拼「需 {前置名} Lv{n}」;达标/未配置=空串。
# 只呈现未达标项:达标后锁标消失,不做全量前置罗列)
func tree_gate_text(node_id: String) -> String:
var row: Dictionary[Variant, Variant] = db.tree_node(node_id)
func _tree_gate_text(node_id: String) -> String:
var row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(node_id)
var rl: Dictionary[Variant, Variant] = row.get("requires_level", {}) as Dictionary
var parts: Array[String] = []
for req_id in rl.keys():
if save.tree_level(String(req_id)) < int(rl[req_id]):
var req_row: Dictionary[Variant, Variant] = db.tree_node(String(req_id))
if meta_state.tree_state().nodes.get(String(req_id), 0) < int(rl[req_id]):
var req_row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(String(req_id))
parts.append("需 %s Lv%d" % [String(req_row.get("display_name", req_id)), int(rl[req_id])])
return "、".join(parts)
func tree_fork_groups() -> Array:
## Fork 行板投影段(原 tree_fork_groups)
func _tree_fork_groups() -> Array:
var out: Array[Variant] = []
var sides: Dictionary[Variant, Variant] = save.fork_sides()
for row in db.tree_rows():
var sides: Dictionary[Variant, Variant] = meta_state.tree_state().fork_sides
for row in db.crossrun.meta_tree.rows:
if String(row.get("kind", "")) != "fork":
continue
var fg := String(row.get("fork_group", ""))
@@ -464,7 +453,7 @@ func tree_fork_groups() -> Array:
"node_id": String(row.get("node_id", "")),
"display_name": String(row.get("display_name", "")),
"active_side": String(sides.get(fg, "")),
"owned": save.tree_level(String(row.get("node_id", ""))) > 0,
"owned": meta_state.tree_state().nodes.get(String(row.get("node_id", "")), 0) > 0,
"can_buy": tree_can_buy(String(row.get("node_id", ""))),
"price": int(row.get("price", 0)),
"sides": [
@@ -478,16 +467,16 @@ func tree_fork_groups() -> Array:
# 彩蛋觉醒检查(特化判定——生命/命中两线拉满 且 同调位 1/2/3 全解锁 → easter_ley 免费点亮;
# 不依赖 requires/requires_level 前置形态)
func _check_easter_egg() -> void:
if save.tree_level("easter_ley") > 0:
if meta_state.tree_state().nodes.get("easter_ley", 0) > 0:
return
for nid in ["sp_hp", "sp_hit"]:
var row: Dictionary[Variant, Variant] = db.tree_node(nid)
if row.is_empty() or save.tree_level(nid) < int(row.get("cap", 1)):
var row: Dictionary[Variant, Variant] = db.crossrun.meta_tree.row_by_id(nid)
if row.is_empty() or meta_state.tree_state().nodes.get(nid, 0) < int(row.get("cap", 1)):
return
for i in range(1, 4):
if save.tree_level("att_socket%d" % i) < 1:
if meta_state.tree_state().nodes.get("att_socket%d" % i, 0) < 1:
return
save.set_tree_level("easter_ley", 1)
meta_state.set_tree_level("easter_ley", 1)
# ---- 同调位账本(装配关系/同调位求值/Y 刷新升级;设计权威 proposal §2)----
@@ -498,30 +487,30 @@ func _check_easter_egg() -> void:
# 格 1=普通格/格 2=优秀格/格 3=稀有格递增向下兼容;槽位来源=树同调位节点点亮数,树接口接缝现役)。
# 装配关系写存档持久。
func toggle_attunement(item_id: String) -> bool:
var equipped: Array[Variant] = save.attunement_equipped()
var equipped: Array[Variant] = meta_state.attune_state().equipped
if not equipped.has(item_id):
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(item_id)
var arow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(item_id)
if arow.is_empty() or String(arow.get("form", "")) == "":
return false # 非同调行(过渡/退役行)禁装配
if not save.item_unlocked(item_id):
if not meta_state.item_unlocked(item_id):
return false # 未解锁禁装配(解锁混合制契约 6——装配以解锁为前置)
if equipped.size() >= Attunement.slots(db, save):
return false # 满位禁选
var rank: int = int(db.treasure_items.TIER_RANK.get(String(arow.get("tier", "")), 99))
var rank: int = int(db.crossrun.treasure_items.TIER_RANK.get(String(arow.get("tier", "")), 99))
if rank > equipped.size() + 1:
return false # 件档高于将占格档(第 k 格档=k——普通/优秀/稀有递增)
equipped.append(item_id)
else:
equipped.erase(item_id)
save.set_attunement_equipped(equipped)
meta_state.set_attunement_equipped(equipped)
return save.write_to_disk()
# 营地装配面投影(账面归本类、拼句归 CampOverlay——_camp_data 同款分工)
func attune_camp_status() -> Dictionary:
var equipped: Array[Variant] = save.attunement_equipped()
var equipped: Array[Variant] = meta_state.attune_state().equipped
var items: Array[Dictionary] = []
for row in db.treasure_attunement_rows():
for row in db.crossrun.treasure_items.attunement_rows():
var iid := String(row.get("item_id", ""))
items.append({
"item_id": iid,
@@ -530,10 +519,10 @@ func attune_camp_status() -> Dictionary:
"tier": String(row.get("tier", "")),
"form": String(row.get("form", "")),
"equipped": equipped.has(iid),
"unlocked": save.item_unlocked(iid),
"unlocked": meta_state.item_unlocked(iid),
})
var lvl := save.attune_refresh_level()
var next_row: Dictionary[Variant, Variant] = db.meta_attune_refresh_row(lvl + 1)
var lvl: int = meta_state.attune_state().refresh_level
var next_row: Dictionary[Variant, Variant] = db.crossrun.meta.attune_refresh_row(lvl + 1)
return {
"slots": Attunement.slots(db, save),
"equipped": equipped,
@@ -549,15 +538,15 @@ func attune_camp_status() -> Dictionary:
# 每冒险开局刷新次数 Y(当前等级行表值;0 级 = 0 次即无刷新能力——P1 甲初始 0 同构)
func attune_refresh_charges() -> int:
return int(db.meta_attune_refresh_row(save.attune_refresh_level()).get("refresh_charges", 0))
return int(db.crossrun.meta.attune_refresh_row(meta_state.attune_state().refresh_level).get("refresh_charges", 0))
# Y 升级购买(直购价 = 下一级行表值 keystone_cost;已满级/余额不足 false 零副作用——buy_forge 同款)
func buy_attune_refresh() -> bool:
var next_row: Dictionary[Variant, Variant] = db.meta_attune_refresh_row(save.attune_refresh_level() + 1)
var next_row: Dictionary[Variant, Variant] = db.crossrun.meta.attune_refresh_row(meta_state.attune_state().refresh_level + 1)
if next_row.is_empty():
return false
if not save.spend_keystone(int(next_row.get("keystone_cost", 0))):
if not meta_state.spend_keystone(int(next_row.get("keystone_cost", 0))):
return false
save.set_attune_refresh_level(save.attune_refresh_level() + 1)
meta_state.set_attune_refresh_level(meta_state.attune_state().refresh_level + 1)
return save.write_to_disk()
+176
View File
@@ -0,0 +1,176 @@
## 存档内元进度数据件(D3 拆件——自 save_manager 收编元进度族):存档内元进度数据的读写唯一层。
## 数据读写原语族(写口 13)+只读投影(读 6——tree/forge/attune/career 四域值拷贝出户);
## 业务规则与写口编排归 MetaProgression,本件只承载键域读写(照单承载,零业务判定)。
## 零 _data 副本零缓存——全部读写经注入 save 的内存数据模型单点。
class_name MetaStateStore
extends RefCounted
var _save # SaveManagerCore 注入引用(_data 唯一取数点)
func _init(save_ref) -> void:
_save = save_ref
# ---- 写口原语族(13)----
# 钥石消费唯一口:余额不足拒绝且零副作用(钥石只增不减——消费除外,元进度验收判据)
func spend_keystone(n: int) -> bool:
if keystone_balance() < n:
return false
_save._data["meta"]["keystone_balance"] = keystone_balance() - n
return true
func unlock_item(item_id: String) -> void:
_save._data["meta"]["item_unlocks"][item_id] = true
func set_tree_level(node_id: String, level: int) -> void:
var tn: Dictionary[Variant, Variant] = _save._data["meta"].get("tree_nodes", {}) as Dictionary
tn[node_id] = maxi(0, level)
_save._data["meta"]["tree_nodes"] = tn
func set_fork_side(group: String, side: String) -> void:
var fs: Dictionary[Variant, Variant] = _save._data["meta"].get("fork_sides", {}) as Dictionary
fs[group] = side
_save._data["meta"]["fork_sides"] = fs
func set_forge_route_level(route_id: String, level: int) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
var lv: Dictionary[Variant, Variant] = dom.get("levels", {}) as Dictionary
lv[route_id] = maxi(0, level)
dom["levels"] = lv
_save._data["meta"]["forge_routes"] = dom
func add_forge_unlock(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
var un: Array[Variant] = dom.get("unlocked", []) as Array
if not un.has(route_id):
un.append(route_id)
dom["unlocked"] = un
_save._data["meta"]["forge_routes"] = dom
func set_forge_weapon(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
dom["weapon"] = route_id
_save._data["meta"]["forge_routes"] = dom
func set_forge_armor(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
dom["armor"] = route_id
_save._data["meta"]["forge_routes"] = dom
func set_attunement_equipped(item_ids: Array) -> void:
var out: Array[Variant] = []
for v in item_ids:
out.append(String(v))
_save._data["meta"]["attunement_equipped"] = out
func set_attune_refresh_level(n: int) -> void:
_save._data["meta"]["attune_refresh_level"] = maxi(0, n)
# 真视类工具累计使用入账(写入唯一经本口冒险收束聚合,受理计数点=battle 层经冒险层聚合)
func add_vision_used(n: int) -> void:
_save._data["meta"]["vision_used"] = maxi(0, career_state()["vision_used"] + maxi(0, n))
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
# 钥石入账(summary.keystone_total——逐杀掉落已在冒险内计入,boss_drops 参数为
# 摘要归档副本不重复入账)+ 逐关卡完成关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
# (同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
# keystone_drops 副本——复盘可归因)+ 生涯终身计数自增(总局数开局即计:
# 三结局同经本口恰计一次;冒险成功局数按 summary.cleared 判定三关全通才计 1。终身计数写入
# 单口=本函数,禁第二写入点)。
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
_save._data["meta"]["adventure_total"] = career_state()["adventure_total"] + 1
if bool(summary.get("cleared", false)):
_save._data["meta"]["adventure_cleared"] = career_state()["adventure_cleared"] + 1
_add_keystone(int(summary.get("keystone_total", 0)))
var cleared := int(summary.get("levels_cleared", 0))
for li in range(1, cleared + 1):
var key := str(li)
_save._data["meta"]["level_clear_counts"][key] = int(_save._data["meta"]["level_clear_counts"].get(key, 0)) + 1
_save._data["meta"]["boss_first_kill"][key] = true
_save._data["meta"]["boss_kill_count"] = career_state()["boss_kill_count"] + cleared # Boss 恒唯一终点:levels_cleared 即本次冒险 Boss 击杀数(含重复)
var entry: Dictionary[Variant, Variant] = summary.duplicate(true)
entry["boss_drops"] = boss_drops.duplicate(true)
var arr: Array[Variant] = _save._data["meta"]["recent_summaries"]
arr.push_front(entry)
while arr.size() > _save.SUMMARY_CAP:
arr.pop_back()
func mark_first_adventure_done() -> void:
_save._data["meta"]["first_adventure_done"] = true
# 钥石入账原语(生产消费零——apply_adventure_summary 专用;测量面经 _ 前缀直调)
func _add_keystone(n: int) -> void:
_save._data["meta"]["keystone_balance"] = keystone_balance() + n
# ---- 只读投影族(6——值拷贝出户,禁碰内部结构契约同款)----
func keystone_balance() -> int:
return int(_save._data["meta"]["keystone_balance"])
func item_unlocked(item_id: String) -> bool:
return _save._data["meta"]["item_unlocks"].has(item_id)
## 天赋树域只读投影:{nodes: node_id→已购等级, fork_sides: fork_group→"a"|"b"}
func tree_state() -> Dictionary:
return {
"nodes": (_save._data["meta"].get("tree_nodes", {}) as Dictionary).duplicate(),
"fork_sides": (_save._data["meta"].get("fork_sides", {}) as Dictionary).duplicate(),
}
## 锻造域只读投影:{levels: 六路线等级, unlocked: 直购清单, weapon/armor: 当前选中}
## (解锁态不落档——派生判定单点在 MetaProgression.forge_route_unlocked)
func forge_state() -> Dictionary:
var dom: Dictionary[Variant, Variant] = _forge_domain()
return {
"levels": (dom.get("levels", {}) as Dictionary).duplicate(),
"unlocked": (dom.get("unlocked", []) as Array).duplicate(),
"weapon": String(dom.get("weapon", "sword")),
"armor": String(dom.get("armor", "light")),
}
## 同调域只读投影:{equipped: 装配 item_id 有序数组(装配序即呈现序), refresh_level: Y 升级等级}
func attune_state() -> Dictionary:
return {
"equipped": (_save._data["meta"].get("attunement_equipped", []) as Array).duplicate(),
"refresh_level": int(_save._data["meta"].get("attune_refresh_level", 0)),
}
## 生涯域只读投影:{clear_counts, boss_first_kill, boss_kill_count, adventure_total,
## adventure_cleared, vision_used, recent_summaries, first_adventure_done}
func career_state() -> Dictionary:
return {
"clear_counts": (_save._data["meta"]["level_clear_counts"] as Dictionary).duplicate(),
"boss_first_kill": (_save._data["meta"]["boss_first_kill"] as Dictionary).duplicate(),
"boss_kill_count": int(_save._data["meta"].get("boss_kill_count", 0)),
"adventure_total": int(_save._data["meta"].get("adventure_total", 0)),
"adventure_cleared": int(_save._data["meta"].get("adventure_cleared", 0)),
"vision_used": int(_save._data["meta"].get("vision_used", 0)),
"recent_summaries": (_save._data["meta"]["recent_summaries"] as Array).duplicate(true),
"first_adventure_done": bool(_save._data["meta"].get("first_adventure_done", true)),
}
# 锻造域取数(本件内部单点)
func _forge_domain() -> Dictionary:
return _save._data["meta"].get("forge_routes", {}) as Dictionary
+1
View File
@@ -0,0 +1 @@
uid://bc5perm32w2yp
+13 -9
View File
@@ -304,15 +304,22 @@ func _do_dig(c: Vector2i) -> Dictionary:
if was_sealed:
_seals.erase(c)
var reveal_order: Array[Variant] = _chain_reveal_order(c, eff_seals)
_reveal_chain(c, reveal_order, was_sealed)
if action_economy.phase != action_economy.PHASE_ACTIVE:
_emit_round_end_seals_and_payload()
return _outcome(true, "executed", "", "")
## 连锁揭示域:首格+连锁新格揭示(BFS 规范序——cell_revealed 发射序即链序,整体随段搬迁)。
## 格面资源资格:chain/dig 恒 true——安全格与危险格一视同仁(危险格亦携带,
## 负向兑付归 战斗结算 极性路由);initial(封锁格)恒 false。资源本体与极性不在本系统。
## dug_under_seal = 首格带封印直挖标记(战斗结算 情况④兑付改判消费;连锁格恒 false——连锁不越封印)
func _reveal_chain(c: Vector2i, reveal_order: Array, was_sealed: bool) -> void:
for rc in reveal_order:
if _revealed.has(rc):
continue # 闭包可穿过已开区——已开格零事件(cell_revealed × |O′ ∖ O|)
var is_mine := _board.is_mine(rc)
var channel := "dig" if rc == c else "chain"
# 格面资源资格:chain/dig 恒 true——安全格与危险格一视同仁(危险格亦携带,
# 负向兑付归 战斗结算 极性路由);initial(封锁格)恒 false。资源本体与极性不在本系统。
# dug_under_seal = 首格带封印直挖标记(战斗结算 情况④兑付改判消费;连锁格恒 false——连锁不越封印)
if is_mine:
if _board.is_mine(rc):
_revealed[rc] = {"kind": "mine", "digit": -1}
cell_revealed.emit({
"cell": rc, "kind": "mine", "digit": -1,
@@ -326,9 +333,6 @@ func _do_dig(c: Vector2i) -> Dictionary:
"variant_ids": _board.variant_ids(rc), "channel": channel, "resource": true,
"dug_under_seal": was_sealed and rc == c,
})
if action_economy.phase != action_economy.PHASE_ACTIVE:
_emit_round_end_seals_and_payload()
return _outcome(true, "executed", "", "")
func _board_face_dig(c: Vector2i) -> String:
@@ -492,7 +496,7 @@ func _emit_seals_batch() -> void:
func _emit_round_end_seals_and_payload() -> void:
_emit_seals_batch()
var payload: Dictionary[Variant, Variant] = action_economy.last_round_end # 逐字段取自 行动经济(读取权 = 接口契约)
var payload: Dictionary[Variant, Variant] = action_economy.last_round_end() # 逐字段取自 行动经济(读取权 = 接口契约;属性→方法 K5)
round_end_board.emit({"path_tag": payload.get("path_tag", ""), "continuation_hint": payload.get("continuation_hint", "")})
+221 -126
View File
@@ -66,6 +66,14 @@ func _setup_and_build(params: Dictionary, seed_val: int) -> Dictionary:
## 参数档可行性判定(执行边界③:参数无解=配置错误)——域校验外加深度预算与战斗保底可放性。
static func check_params(params: Dictionary) -> Array:
var errs: Array[Dictionary] = _cp_domain(params)
if not errs.is_empty():
return errs
return _cp_structural(params)
## 域校验段:五旋钮值域/岔口去路数锁值/配比四类合计 1
static func _cp_domain(params: Dictionary) -> Array[Dictionary]:
var errs: Array[Dictionary] = []
var L := int(params.get("layers", 0))
var e := int(params.get("exit_count", 0))
@@ -89,8 +97,16 @@ static func check_params(params: Dictionary) -> Array:
sum += float(params.get(rk, -1.0))
if not is_equal_approx(sum, 1.0):
errs.append({"category": "domain", "message": "配比四类合计 %f ≠ 1" % sum})
if not errs.is_empty():
return errs
return errs
## 结构可行性段:主链段容量与战斗保底可放性(多出口按区域签名累加可放槽)
static func _cp_structural(params: Dictionary) -> Array[Dictionary]:
var errs: Array[Dictionary] = []
var L := int(params.get("layers", 0))
var e := int(params.get("exit_count", 0))
var en := int(params.get("entry_count", 0))
var bf := int(params.get("battle_floor", -1))
var t := 0 if e == 1 else _trunk_len(L, e)
if e > 1 and t < 1:
errs.append({"category": "structural", "message": "层深 %d 与出口数 %d 无解(主链段放不下归属区域)" % [L, e]})
@@ -116,11 +132,33 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
return errs
var by_id := {}
var indeg := {}
if not _vo_node_fields(nodes, errs, by_id, indeg):
return errs
if not _vo_edges(nodes, by_id, indeg, errs):
return errs
var flow := _vo_flow_facts(nodes, indeg, errs)
if not flow["ok"]:
return errs
if not _vo_depth_reach(nodes, by_id, flow["entries"], flow["terminals"], errs):
return errs
_vo_shop_counts(nodes, flow["terminals"], errs)
_vo_terminal_shops(nodes, flow["terminals"], errs)
if not _all_paths_one_shop(by_id, flow["entries"], flow["terminals"]):
errs.append({"category": "structural", "message": "存在入口→终点路径穿店数 ≠ 1(必经恰一店破)"})
_check_fork_rules(nodes, by_id, errs)
_vo_terminal_depths(flow["terminals"], by_id, errs)
_vo_width_rules(nodes, errs)
_vo_params_check(params, nodes, by_id, [flow["entries"], flow["terminals"]], errs)
return errs
## 阶段一·节点字段域:字段齐备/句柄唯一/类型词表/深度非负(by_id/indeg 引用填充)
func _vo_node_fields(nodes: Array, errs: Array, by_id: Dictionary, indeg: Dictionary) -> bool:
for n in nodes:
for k in ["id", "type", "depth", "next"]:
if not (n as Dictionary).has(k):
errs.append({"category": "structural", "message": "节点字段缺失: %s" % k})
return errs
return false
var id: int = int(n["id"])
if by_id.has(id):
errs.append({"category": "dup_key", "message": "节点句柄重复: %d" % id})
@@ -130,8 +168,11 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
if int(n["depth"]) < 0:
errs.append({"category": "domain", "message": "节点深度为负: %d" % n["depth"]})
indeg[id] = 0
if not errs.is_empty():
return errs
return errs.is_empty()
## 阶段二·边域:出边悬空与环/回边判据(indeg 引用累计)
func _vo_edges(nodes: Array, by_id: Dictionary, indeg: Dictionary, errs: Array) -> bool:
for n in nodes:
for oid in (n["next"] as Array):
if not by_id.has(int(oid)):
@@ -141,11 +182,13 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
errs.append({"category": "structural", "message": "连线未朝前进方向(目标深度 %d 不严格大于源 %d)——环/回边" % [
int(by_id[int(oid)]["depth"]), int(n["depth"])]})
indeg[int(oid)] = int(indeg[int(oid)]) + 1
if not errs.is_empty():
return errs
return errs.is_empty()
var entries := []
var terminals := []
## 阶段三·流事实域:入口集(入度 0)/终点集(无出边)与 Boss 终点判据、去路数形态
func _vo_flow_facts(nodes: Array, indeg: Dictionary, errs: Array) -> Dictionary:
var entries: Array[int] = []
var terminals: Array[int] = []
for n in nodes:
var id0: int = int(n["id"])
var outs = n["next"]
@@ -159,11 +202,12 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
errs.append({"category": "structural", "message": "Boss 节点带出边: %d" % id0})
elif outs.size() != 1 and not (outs.size() >= 2 and outs.size() <= 3):
errs.append({"category": "domain", "message": "节点 %d 去路数 %d 非〔恰一条或岔口 2–3〕" % [id0, outs.size()]})
if not errs.is_empty():
return errs
return {"entries": entries, "terminals": terminals, "ok": errs.is_empty()}
# 深度=自入口最长路径步数(契约 2 赋值语义——出边严格前进的无环判据成立的前提)
var longest := _longest_paths(nodes, by_id, entries, indeg)
## 阶段四·深度与可达域:深度=自入口最长路径步数(契约 2)+全节点可达某终点
func _vo_depth_reach(nodes: Array, by_id: Dictionary, entries: Array, terminals: Array, errs: Array) -> bool:
var longest := _longest_paths(nodes, by_id, entries)
for n in nodes:
var id1: int = int(n["id"])
if int(n["depth"]) != int(longest.get(id1, -1)):
@@ -175,16 +219,21 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
for n in nodes:
if not reaches.has(int(n["id"])):
errs.append({"category": "structural", "message": "节点 %d 不可达任何终点(断头/孤儿)" % n["id"]})
if not errs.is_empty():
return errs
return errs.is_empty()
# 摆放约束①:每终点前一位恰一店、商店总数=终点数、终点前一层去路集合恰含一个终点
## 摆放约束①前半·商店总数:商店总数=终点数(商店数=出口数已锁)
func _vo_shop_counts(nodes: Array, terminals: Array, errs: Array) -> void:
var shop_count := 0
for n in nodes:
if String(n["type"]) == "shop":
shop_count += 1
if shop_count != terminals.size():
errs.append({"category": "structural", "message": "商店总数 %d ≠ 终点数 %d(商店数=出口数已锁)" % [shop_count, terminals.size()]})
## 摆放约束①后半·终点前驱:每终点前一位恰一店且为唯一前驱、终点前一层去路集合恰含一个终点
func _vo_terminal_shops(nodes: Array, terminals: Array, errs: Array) -> void:
for tid in terminals:
var preds := []
for n in nodes:
@@ -204,16 +253,19 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
if t_hits != 1:
errs.append({"category": "structural", "message": "终点 %d 前一层去路集合含终点 %d 个(归属锁定破)" % [tid, t_hits]})
if not _all_paths_one_shop(by_id, entries, terminals):
errs.append({"category": "structural", "message": "存在入口→终点路径穿店数 ≠ 1(必经恰一店破)"})
_check_fork_rules(nodes, by_id, errs)
## 计数规则①·多终点深度互异(推论条款 A——定位信息只有两样)
func _vo_terminal_depths(terminals: Array, by_id: Dictionary, errs: Array) -> void:
var tdepths := {}
for tid in terminals:
var dd: int = int(by_id[tid]["depth"])
if tdepths.has(dd):
errs.append({"category": "structural", "message": "多终点同深度 %d(推论条款 A 破——定位信息只有两样)" % dd})
tdepths[dd] = true
## 计数规则②·宽度上限:同深度节点数 ≤ 分叉生长上限 4(禁平行层摆放)
func _vo_width_rules(nodes: Array, errs: Array) -> void:
var per_depth := {}
for n in nodes:
var d2: int = int(n["depth"])
@@ -222,23 +274,28 @@ func validate_output(graph: Dictionary, params: Dictionary = {}) -> Array:
if int(per_depth[d3]) > MAX_WIDTH:
errs.append({"category": "structural", "message": "深度 %d 节点数 %d 超分叉生长上限 4(平行层摆放)" % [d3, per_depth[d3]]})
## 计数规则③·参数档对照:战斗保底/入口数/终点数/层高(params 为空时跳过)
func _vo_params_check(params: Dictionary, nodes: Array, by_id: Dictionary, flow_ends: Array, errs: Array) -> void:
var entries: Array[int] = flow_ends[0]
var terminals: Array[int] = flow_ends[1]
var battles := 0
for n in nodes:
if String(n["type"]) == "battle":
battles += 1
if not params.is_empty():
if battles < int(params.get("battle_floor", 0)):
errs.append({"category": "domain", "message": "战斗节点数 %d 低于保底 %d" % [battles, params.get("battle_floor", 0)]})
if entries.size() != int(params.get("entry_count", entries.size())):
errs.append({"category": "structural", "message": "入口数 %d ≠ 参数档 entry_count %d" % [entries.size(), params.get("entry_count", 0)]})
if terminals.size() != int(params.get("exit_count", terminals.size())):
errs.append({"category": "structural", "message": "终点数 %d ≠ 参数档 exit_count %d" % [terminals.size(), params.get("exit_count", 0)]})
var maxd := 0
for tid in terminals:
maxd = maxi(maxd, int(by_id[tid]["depth"]))
if maxd != int(params.get("layers", maxd)):
errs.append({"category": "domain", "message": "层高(最长路径步数)%d ≠ 参数档 layers %d" % [maxd, params.get("layers", 0)]})
return errs
if params.is_empty():
return
if battles < int(params.get("battle_floor", 0)):
errs.append({"category": "domain", "message": "战斗节点数 %d 低于保底 %d" % [battles, params.get("battle_floor", 0)]})
if entries.size() != int(params.get("entry_count", entries.size())):
errs.append({"category": "structural", "message": "入口数 %d ≠ 参数档 entry_count %d" % [entries.size(), params.get("entry_count", 0)]})
if terminals.size() != int(params.get("exit_count", terminals.size())):
errs.append({"category": "structural", "message": "终点数 %d ≠ 参数档 exit_count %d" % [terminals.size(), params.get("exit_count", 0)]})
var maxd := 0
for tid in terminals:
maxd = maxi(maxd, int(by_id[tid]["depth"]))
if maxd != int(params.get("layers", maxd)):
errs.append({"category": "domain", "message": "层高(最长路径步数)%d ≠ 参数档 layers %d" % [maxd, params.get("layers", 0)]})
# ---- 构型(长图算法 HOW 归实现——D1:28;合法性由 validate_output 强制)----
@@ -252,28 +309,11 @@ func _build(rg: RandomNumberGenerator) -> Dictionary:
var entries: Array[int] = []
for i in range(en):
entries.append(_mk("battle", 0))
var prevs: Array[int] = entries.duplicate()
for d in range(1, t + 1):
var nid := _mk("battle", d)
for p in prevs:
(_by_id[p]["next"] as Array).append(nid)
prevs = [nid]
var prevs: Array[int] = _build_trunk(t, entries.duplicate())
var terminals: Array[int] = []
for r in range(e):
var bdepth := L - r
var bid := _mk("boss", bdepth)
var sid := _mk("shop", bdepth - 1)
(_by_id[sid]["next"] as Array).append(bid)
var cur := sid
var sig := String(REGION_SIGNATURES[r]) if e > 1 else "battle"
for d in range(bdepth - 2, t, -1):
var fid := _mk(sig, d)
(_by_id[fid]["next"] as Array).append(cur)
cur = fid
terminals.append(bid)
for p in prevs:
(_by_id[p]["next"] as Array).append(cur)
_build_region(r, e, L, prevs, terminals)
# e>1 时 prevs 收束为主链段末节点一处 ⇒ 该节点即归属大岔口(各区域互不连达);
# e==1 时 prevs 为入口数组,直接接单区域头(无大岔口——L1 出口恒 1)。
@@ -281,41 +321,61 @@ func _build(rg: RandomNumberGenerator) -> Dictionary:
_assign_types(rg, e)
var nodes: Array[Dictionary] = []
for n in _pool:
nodes.append(n)
nodes.assign(_pool)
return {"nodes": nodes, "entries": entries, "terminals": terminals}
## 主链段(深度 1..t 逐层单链,主链段零 RNG):返回链末节点集(e>1 时收束为一处即归属大岔口)
func _build_trunk(t: int, prevs: Array[int]) -> Array[int]:
for d in range(1, t + 1):
var nid := _mk("battle", d)
for p in prevs:
(_by_id[p]["next"] as Array).append(nid)
prevs = [nid]
return prevs
## 归属区域段(每出口一条支路:Boss 终点+店+签名填充链;区域段零 RNG):
## Boss 深度 = L−r、店在其前一位、签名链自店向主链段方向回铺,支路头接入 prevs(归属大岔口)
func _build_region(r: int, e: int, L: int, prevs: Array, terminals: Array) -> void:
var t := 0 if e == 1 else _trunk_len(L, e)
var bdepth := L - r
var bid := _mk("boss", bdepth)
var sid := _mk("shop", bdepth - 1)
(_by_id[sid]["next"] as Array).append(bid)
var cur := sid
var sig := String(REGION_SIGNATURES[r]) if e > 1 else "battle"
for d in range(bdepth - 2, t, -1):
var fid := _mk(sig, d)
(_by_id[fid]["next"] as Array).append(cur)
cur = fid
terminals.append(bid)
for p in prevs:
(_by_id[p]["next"] as Array).append(cur)
## 岔口布置:主链段(深度 ≤ t-2)或单出口图链上(深度 ≤ L-3)的节点扩为 2–3 去路岔口;
## 侧支节点与主去路同深(保持「深度=自入口最长路径步数」不变量),两支各自汇入主去路的后继,
## 于是支路互斥段={主去路节点} 与 {侧支节点}——两支类型互异即同时满足「下一跳互异」与「各有独有类型」。
func _add_forks(rg: RandomNumberGenerator, L: int, t: int, e: int) -> void:
var wanted := clampi(int(round(float(_params["branch_density"]) * float(L))), 1, 24)
var anchors := []
for n in _pool:
if int(n["depth"]) > (t - 2 if e > 1 else L - 3):
continue
if (n["next"] as Array).size() != 1:
continue
anchors.append(n)
var anchors := _collect_fork_anchors(L, t, e)
anchors = _shuffle(rg, anchors)
var selected := {} # 已扩岔节点 id
var succ_selected := {} # 已扩岔节点的主去路 id(禁再作锚点)
for a in anchors:
if wanted <= 0:
break
var node = a
var nid := int(node["id"])
if (node["next"] as Array).size() != 1:
var nid := int(a["id"])
if (a["next"] as Array).size() != 1:
continue # 同一节点不重复扩岔(去路数上限 3)
var v = _by_id[int((node["next"] as Array)[0])]
var v = _by_id[int((a["next"] as Array)[0])]
var vid := int(v["id"])
# 相邻两层不得同时扩岔:若主去路 v 也是岔口,v 的侧支会落进本岔口主支路的互斥段,
# 使侧支自己的类型不再独有(契约 5③)——两个方向都要拦。
if selected.has(vid) or succ_selected.has(nid):
continue
var v_outs = v["next"]
if v_outs.is_empty():
if (v["next"] as Array).is_empty():
continue # 主去路无后继(无可汇入点)——该锚点弃用
var extra := 2 if (int(_params["fork_ways_max"]) >= 3 and rg.randf() < 0.35) else 1
var made := 0
@@ -323,8 +383,8 @@ func _add_forks(rg: RandomNumberGenerator, L: int, t: int, e: int) -> void:
if _depth_count(int(v["depth"])) >= MAX_WIDTH:
break
var wid := _mk("battle", int(v["depth"]))
(node["next"] as Array).append(wid)
(_by_id[wid]["next"] as Array).append(int(v_outs[0]))
(a["next"] as Array).append(wid)
(_by_id[wid]["next"] as Array).append(int((v["next"] as Array)[0]))
_detour_heads.append(_by_id[wid])
_detour_ids[wid] = true
made += 1
@@ -334,6 +394,18 @@ func _add_forks(rg: RandomNumberGenerator, L: int, t: int, e: int) -> void:
wanted -= 1
## 锚收集段(零 RNG):主链段(深度 ≤ t-2)或单出口图链上(深度 ≤ L-3)的去路恰一节点
func _collect_fork_anchors(L: int, t: int, e: int) -> Array:
var anchors := []
for n in _pool:
if int(n["depth"]) > (t - 2 if e > 1 else L - 3):
continue
if (n["next"] as Array).size() != 1:
continue
anchors.append(n)
return anchors
# 确定性洗牌(Fisher-Yates,取数只经注入的纯流 RNG——Array.shuffle() 走引擎全局随机、
# 破「同(种子,参数档)⇒ 同图」的纯函数复现前提,禁用于生成器)
func _shuffle(rg: RandomNumberGenerator, arr: Array) -> Array:
@@ -358,31 +430,42 @@ func _depth_count(d: int) -> int:
## 单出口图的配比填余(摆放优先、配比填余、战斗保底兜底——§8.1 次序),再统一修岔口下一跳互异。
func _assign_types(rg: RandomNumberGenerator, e: int) -> void:
if e == 1:
var free: Array[Dictionary] = []
var chain_battles := 0
for n in _pool:
if String(n["type"]) == "shop" or String(n["type"]) == "boss":
continue
if _detour_ids.has(int(n["id"])):
continue # 侧链节点类型由岔口互异修型决定,不占配比填余面
if int(n["depth"]) == 1:
continue # 区域首节点留战斗——配比填余不破坏岔口下一跳互异的基准形
if String(n["type"]) == "battle":
chain_battles += 1
free.append(n)
free = _shuffle(rg, free)
var want := int(round(float(_pool.size()) * (float(_params["reward_ratio"]) + float(_params["rest_ratio"]))))
var floor_n := int(_params["battle_floor"])
var rest_first := rg.randf() < 0.5
for i in range(want):
if i >= free.size():
break
# 战斗保底优先:链上战斗数降到保底下限即停(侧链修型可能再让出战斗位,故保底只看链上)
if chain_battles - 1 < floor_n:
break
var node = free[i]
node["type"] = "rest" if ((i % 2 == 0) == rest_first) else "reward"
chain_battles -= 1
_fill_ratios(rg)
_fix_detour_heads()
## 配比填余段(单出口图;shuffle+randf 取号序整体搬迁——战斗保底优先、侧链不占填余面):
## 摆放优先、配比填余、战斗保底兜底——§8.1 次序
func _fill_ratios(rg: RandomNumberGenerator) -> void:
var free: Array[Dictionary] = []
var chain_battles := 0
for n in _pool:
if String(n["type"]) == "shop" or String(n["type"]) == "boss":
continue
if _detour_ids.has(int(n["id"])):
continue # 侧链节点类型由岔口互异修型决定,不占配比填余面
if int(n["depth"]) == 1:
continue # 区域首节点留战斗——配比填余不破坏岔口下一跳互异的基准形
if String(n["type"]) == "battle":
chain_battles += 1
free.append(n)
free = _shuffle(rg, free)
var want := int(round(float(_pool.size()) * (float(_params["reward_ratio"]) + float(_params["rest_ratio"]))))
var floor_n := int(_params["battle_floor"])
var rest_first := rg.randf() < 0.5
for i in range(want):
if i >= free.size():
break
# 战斗保底优先:链上战斗数降到保底下限即停(侧链修型可能再让出战斗位,故保底只看链上)
if chain_battles - 1 < floor_n:
break
var node = free[i]
node["type"] = "rest" if ((i % 2 == 0) == rest_first) else "reward"
chain_battles -= 1
## 岔口修型段(零 RNG):侧链首节点类型取岔口各去路下一节点类型的补集首件——下一跳互异承面
func _fix_detour_heads() -> void:
for head in _detour_heads:
var h = head
var fork = null
@@ -416,12 +499,15 @@ static func _trunk_len(L: int, e: int) -> int:
# ---- 自证辅助 ----
func _longest_paths(nodes: Array, by_id: Dictionary, entries: Array, indeg: Dictionary) -> Dictionary:
func _longest_paths(nodes: Array, by_id: Dictionary, entries: Array) -> Dictionary:
var out := {}
var din := {}
var q := []
for id in indeg:
din[int(id)] = int(indeg[int(id)])
for n in nodes:
din[int(n["id"])] = 0
for n in nodes:
for oid in (n["next"] as Array):
din[int(oid)] = int(din[int(oid)]) + 1
for eid in entries:
out[int(eid)] = 0
q.append(int(eid))
@@ -476,44 +562,53 @@ func _check_fork_rules(nodes: Array, by_id: Dictionary, errs: Array) -> void:
if head_types.has(ty):
errs.append({"category": "structural", "message": "岔口 %d 各去路下一节点类型重复: %s" % [n["id"], ty]})
head_types[ty] = true
var reach_sets := []
for oid in outs:
reach_sets.append(_descendants(int(oid), by_id))
var comps := []
for i in range(reach_sets.size()):
var excl := {}
for rid in reach_sets[i]:
var shared := false
for j in range(reach_sets.size()):
if j != i and reach_sets[j].has(rid):
shared = true
break
if not shared:
excl[int(rid)] = String(by_id[int(rid)]["type"])
var arr := []
for v in excl.values():
arr.append(v)
arr.sort()
comps.append(arr)
var comps := _fork_signatures(outs, by_id)
for i in range(comps.size()):
for j in range(i + 1, comps.size()):
if comps[i] == comps[j]:
errs.append({"category": "structural", "message": "岔口 %d 支路 %d/%d 类型构成相同" % [n["id"], i, j]})
for i in range(comps.size()):
var uniq := false
for ty in comps[i]:
var in_other := false
for j in range(comps.size()):
if j != i and (comps[j] as Array).has(ty):
in_other = true
break
if not in_other:
uniq = true
break
if not uniq:
if not _fork_has_unique(comps, i):
errs.append({"category": "structural", "message": "岔口 %d 支路 %d 无独有类型" % [n["id"], i]})
## 支路签名构造:各去路的独占后代类型集(他人不可达的后代类型,排序数组——支路互斥段签名)
func _fork_signatures(outs: Array, by_id: Dictionary) -> Array:
var reach_sets := []
for oid in outs:
reach_sets.append(_descendants(int(oid), by_id))
var comps := []
for i in range(reach_sets.size()):
var excl := {}
for rid in reach_sets[i]:
var shared := false
for j in range(reach_sets.size()):
if j != i and reach_sets[j].has(rid):
shared = true
break
if not shared:
excl[int(rid)] = String(by_id[int(rid)]["type"])
var arr := []
for v in excl.values():
arr.append(v)
arr.sort()
comps.append(arr)
return comps
## 独有类型判定:支路 i 的签名中存在他支路不含的类型
func _fork_has_unique(comps: Array, i: int) -> bool:
for ty in comps[i]:
var in_other := false
for j in range(comps.size()):
if j != i and (comps[j] as Array).has(ty):
in_other = true
break
if not in_other:
return true
return false
func _descendants(start: int, by_id: Dictionary) -> Dictionary:
var out := {start: true}
var q := [start]
+2 -2
View File
@@ -81,8 +81,8 @@ func reset_paid() -> void:
# 本次 GP 付费刷新价(与商店货架刷新同源:ceil(基价 × 曲线^本次停点已付费刷))
func gp_cost() -> int:
return int(ceil(float(db.adventure.shop_refresh_cost_base)
* pow(db.adventure.shop_refresh_cost_curve, float(_paid_refreshes))))
return int(ceil(float(db.run.adventure.shop_refresh_cost_base)
* pow(db.run.adventure.shop_refresh_cost_curve, float(_paid_refreshes))))
# 冒险级复位(宿主 reset_adventure_state 调用——刷新三源与付费计数全零)
+40 -24
View File
@@ -40,18 +40,8 @@ func roll_board(eligible: Array) -> bool:
if eligible.is_empty():
_fatal("roll_board 空资格清单(E1 兜底 fail-fast——排雷玩法 不变式④前置)")
return false
# 行主序规范化 + 去重(排序责任不外推给 战斗结算;排序键单点 BoardData.row_major_key)
var seen := {}
var list: Array[Dictionary] = []
for e in eligible:
var c: Vector2i = e.cell
if seen.has(c):
push_warning("[地图战斗资源] 资格清单重复格已去重: %s" % str(c))
continue
seen[c] = true
list.append({"cell": c})
list.sort_custom(func(a, b): return BoardData.row_major_key(a.cell) < BoardData.row_major_key(b.cell))
_eligible = list
_eligible = _normalize_eligible(eligible)
var list: Array[Dictionary] = _eligible
_granted.clear() # 新批重置映射受理面(旧图快照消亡)
_power_roll.clear()
_clear_power = -1
@@ -85,6 +75,21 @@ func roll_board(eligible: Array) -> bool:
return true
## 资格清单规范化域(开流前——零 RNG):行主序 + 去重(排序责任不外推给 战斗结算;排序键单点 BoardData.row_major_key)
func _normalize_eligible(eligible: Array) -> Array[Dictionary]:
var seen := {}
var list: Array[Dictionary] = []
for e in eligible:
var c: Vector2i = e.cell
if seen.has(c):
push_warning("[地图战斗资源] 资格清单重复格已去重: %s" % str(c))
continue
seen[c] = true
list.append({"cell": c})
list.sort_custom(func(a, b): return BoardData.row_major_key(a.cell) < BoardData.row_major_key(b.cell))
return list
# ---- 查询面(Ready 只读;未开格恒可查——秘宝 桥接结构前提)----
# 相位只读出口(诊断观测面——unrolled/ready/retired;消费方禁直读 _phase 下划线字段)
@@ -167,7 +172,7 @@ func retire_mapping() -> bool:
# ---- 极性路由字典面(战斗结算 授予段消费):(类型, 极性)→ 格面池字典行(拷贝出口)----
func polarity_row(cell_type: String, polarity: String) -> Dictionary:
for row in db.reward.rows:
for row in db.run.reward.rows:
if String(row.get("pool_tag", "")) == "cell" \
and String(row.get("cell_type", "")) == cell_type \
and String(row.get("polarity", "")) == polarity:
@@ -217,6 +222,20 @@ func _skewed_cell_rows() -> Array:
if board_skew <= 0:
return _rollable_rows()
var base: Array[Variant] = _rollable_rows()
var families := _skew_families(base)
if families["down"].is_empty() or families["up"].is_empty():
return base # 结构缺族 = 无偏斜作用面(保守直通——池结构变更须走表加载校验另批)
# 下调总量 = round_half_up(Σ生存×v/100);逐行精确份额 w×v/100 最大余数取整(行序平局)
var drops := _largest_remainder_split(families["down"], board_skew, 100, (2 * _pool_weight(families["down"]) * board_skew + 100) / 200)
# 上浮总量 = 下调总量(守恒),逐行精确份额 drop×w/Σ进攻 最大余数取整
var gains := _largest_remainder_split(families["up"], drops_total(drops), _pool_weight(families["up"]), drops_total(drops))
var out := _apply_skew(base, families["down"], families["up"], drops, gains)
assert(_pool_weight(out) == _pool_weight(base), "图级偏斜守恒破(偏斜后合计须等于原池)")
return out
## 分族域:生存下调族与进攻上浮族行集拆分(池结构不变式零触碰)
func _skew_families(base: Array) -> Dictionary:
var down: Array[Variant] = []
var up: Array[Variant] = []
for row in base:
@@ -225,13 +244,11 @@ func _skewed_cell_rows() -> Array:
down.append(row)
elif SKEW_UP_KINDS.has(k):
up.append(row)
if down.is_empty() or up.is_empty():
return base # 结构缺族 = 无偏斜作用面(保守直通——池结构变更须走表加载校验另批)
var v := board_skew
# 下调总量 = round_half_up(Σ生存×v/100);逐行精确份额 w×v/100 最大余数取整(行序平局)
var drops := _largest_remainder_split(down, v, 100, (2 * _pool_weight(down) * v + 100) / 200)
# 上浮总量 = 下调总量(守恒),逐行精确份额 drop×w/Σ进攻 最大余数取整
var gains := _largest_remainder_split(up, drops_total(drops), _pool_weight(up), drops_total(drops))
return {"down": down, "up": up}
## 权重装配域:逐行精确份额增减(下调族扣 drops、上浮族加 gains——守恒 assert 随段迁移)
func _apply_skew(base: Array, down: Array, up: Array, drops: Array, gains: Array) -> Array:
var di := 0
var ui := 0
var out: Array[Variant] = []
@@ -248,7 +265,6 @@ func _skewed_cell_rows() -> Array:
var skewed: Dictionary[Variant, Variant] = row.duplicate()
skewed["weight"] = w
out.append(skewed)
assert(_pool_weight(out) == _pool_weight(base), "图级偏斜守恒破(偏斜后合计须等于原池)")
return out
@@ -309,7 +325,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
func _rollable_rows() -> Array:
# 摇号池 = cell 池正向行 weight>0(负向行纯字典不消费权重;封锁格不在本函数职责域——清单由调用方裁剪)
var out: Array[Dictionary] = []
for row in db.reward.rows:
for row in db.run.reward.rows:
if String(row.get("cell_type", "")) == RewardTable.TREASURE_ITEM_TYPE:
var iid := String(row.get("effect_id", ""))
if iid == "" or not _treasure_unlocked.has(iid):
@@ -321,7 +337,7 @@ func _rollable_rows() -> Array:
func _pool_rows(pool_tag: String) -> Array:
var out: Array[Dictionary] = []
for row in db.reward.rows:
for row in db.run.reward.rows:
if row.pool_tag == pool_tag and int(row.weight) > 0:
out.append(row)
return out
@@ -335,7 +351,7 @@ func _pool_weight(rows: Array) -> int:
func _row_by_id(rid: String) -> Dictionary:
for row in db.reward.rows:
for row in db.run.reward.rows:
if row.reward_id == rid:
return row
return {}
+10 -4
View File
@@ -120,7 +120,7 @@ func _charge_gate(v: Dictionary, target: Vector2i) -> Dictionary:
for h in tu_hits:
if String(h.get("kind", "")) == "counter_spend":
if not EffectEngine.counter_spend(fx_host.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 1))):
var vrow: Dictionary[Variant, Variant] = db.treasure_item_row(String(v.item_id))
var vrow: Dictionary[Variant, Variant] = db.crossrun.treasure_items.row_by_id(String(v.item_id))
var cn_info: int = int(vrow.get("charge_n", 0)) # 读受理行自身
var seen := int(attune_safe_seen_cb.call())
return {"ok": false, "reason": "%s:安全格积点未达(%d/%d)" % [String(vrow.get("display_name", "")), seen % cn_info if cn_info > 0 else 0, cn_info]}
@@ -199,8 +199,8 @@ func grant_routed(snap: Dictionary, polarity: String, att: Dictionary) -> bool:
# premium_pct 贯穿 _apply_percent_row 应用与呈现同乘)——标空/挖格/清图不乘,雷安同池冻结不动。
# 取整=四舍五入(与伤害管线 round_direction=floor 分域——registry clause3_inequality 登记;
# 1 点量纲行 ac_up/action 取整后不变=整数粒度地板)。
if source_class == "seal_hit" and int(db.settlement.core.seal_hit_premium) != 100:
var prem_pct: int = int(db.settlement.core.seal_hit_premium)
if source_class == "seal_hit" and int(db.combat.settlement.core.seal_hit_premium) != 100:
var prem_pct: int = int(db.combat.settlement.core.seal_hit_premium)
row = row.duplicate()
row["power"] = int(round(int(row.power) * prem_pct / 100.0))
row["premium_pct"] = prem_pct
@@ -229,7 +229,7 @@ func _apply_percent_row(row: Dictionary, att: Dictionary) -> bool:
var eff := String(row.effect_id)
if eff == "gp_gain" or eff == "gp_loss":
return _apply_gp_row(row, att)
var srow: Dictionary[Variant, Variant] = db.status_effect_row(eff)
var srow: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff)
# 封印溢价贯穿(仅封印命中正向行携带 premium_pct;其余授予路径缺省 100 零扰动):
# 应用幅度与呈现行值同乘,容器经 magnitude_override 拿同一快照(抗表重载口径保持)。
var prem_pct: int = int(row.get("premium_pct", 100))
@@ -365,6 +365,12 @@ func outcome_of(ticket_id: int) -> String:
return "redeemed" if ticket_id < 0 else damage.outcome(ticket_id)
# 延迟单据入队(迭代 46 卡A T4——成功刷新的崩壁冲击群伤单走案C 同队列:授予段闸外唯一入口,
# 战斗段末 _board_deferred_settle 按开单序补提交统一结账;类变量零增员——复用 defer.queue)
func defer_ticket(t: Dictionary) -> void:
(defer.queue as Array).append(t)
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
func _submit_flat_hero(display: String, power: int, att: Dictionary, sync: bool) -> String:
var t := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, String(att.get("source_class", "")), display,
+143 -519
View File
@@ -4,6 +4,8 @@
## 禁 preload/import 任何玩法脚本、禁碰 ConfigDB/RngService——须可独立实例化独立验证。
## 新增存档键先在序列化函数登记并升版本号;键域内容口径归各 GDD 文档,本类照单承载。
## 存档键清单的事实源=本类序列化/读回代码,不在任何文档维护第二份键清单。
## 拆件形态(D3 存档域):槽位文件域归 SlotStore(slots)、存档内元进度数据读写归 MetaStateStore
## (meta_state)——本类留磁盘 IO+版本迁移链+断点域+门面装配,消费方经 save.slots / save.meta_state 直连。
class_name SaveManagerCore
extends Node
@@ -18,18 +20,18 @@ const SLOT_PATH_TEMPLATE := "user://save_slot_%d.json" # 槽文件名唯一派
const LEGACY_SAVE_PATH := "user://save.json" # 旧隐式单档路径(Q1 甲:首次启动一次性字节迁入槽 1,此后无任何隐式读写)
const LEGACY_MIGRATED_SUFFIX := ".migrated.bak" # 迁移后旧档改名留痕后缀(字节级留盘不删——处置留痕)
var _path: String # 当前写盘目标=激活槽文件(或 attach_path 注入路径);""=无激活态——一切写盘路径拒绝
var _active_slot := 0 # 激活槽号 1..SLOT_COUNT;0=无激活态(注入非槽派生名路径时亦 0——写盘判定以 _path 非空为准)
var _read_only := false # 高版本拒读置位——write_to_disk 一律拒绝(契约 4 禁写)
var _data: Dictionary[String, Variant] = {} # 内存数据模型(_init 即内置默认;load 后为档案内容)
var _migrations: Dictionary[int, Callable] = {} # from_version → Callable(data)->data(升一版;v1→v6 生产迁移链现行——_init 自注册)
var _slot_template := SLOT_PATH_TEMPLATE # 槽文件名模板(生产=常量;测试经 attach_slot_template 注入隔离命名空间,禁在呈现层/探针各自拼槽名)
var slots: SlotStore # 槽位文件域件(槽位挂载与模板/激活与建删/摘要窥视——架构 :89)
var meta_state: MetaStateStore # 存档内元进度数据读写唯一层(写口原语+只读投影——架构 :90)
var _migration_fail_reason := "" # 迁移循环失败原因带出(_peek_migrated 返 null 时 load 侧落 corrupt 兜底文案单点)
func _init(path: String = "") -> void:
_path = path
_active_slot = _slot_index_of(path)
_data = _default_data()
slots = SlotStore.new(self, path)
meta_state = MetaStateStore.new(self)
# 生产迁移链自注册(v2 起——测试钩 register_migration 仍可叠挂更早版本步骤)
register_migration(1, _migrate_v1_v2)
register_migration(2, _migrate_v2_v3)
@@ -43,176 +45,103 @@ func _init(path: String = "") -> void:
# 类名加 Core 后缀、autoload 占净名)。独立 .new() 实例(单测/工具)不入树零 _ready,
# 读盘仍由调用方显式触发。
# 启动态钉死(卡① sv2):autoload 启动不读任何槽——内存=默认合法空态(_init 置好)、
# 无激活态(_path="",一切写盘路径拒绝并返回 false);玩家侧经选档界面显式进档/新档后才可写。
# 无激活态(注入路径空,一切写盘路径拒绝并返回 false);玩家侧经选档界面显式进档/新档后才可写。
# 旧隐式单档一次性迁入槽 1(Q1 甲,sv12)在本口执行。
func _ready() -> void:
_migrate_legacy_save(LEGACY_SAVE_PATH, slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX)
slots._migrate_legacy_save(LEGACY_SAVE_PATH, slots.slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX)
# 原地重置(探针隔离面,仿 RngService.set_master_seed 单例原地重置:
# 换路径+回默认数据+重读盘;对象引用恒定,MetaProgression 持有的 save 引用零失效)。
# 三槽化后单路径语义(契约 7)=注入路径即激活槽文件(写盘落该路径、无激活态时注入即激活);
# 注入槽派生名时激活号同步记账,注入任意隔离路径时激活号 0(写盘仍合法——隔离面行为零变化)。
func attach_path(path: String) -> void:
_path = path
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
# Dictionary → Dictionary,须写回 version = from_version + 1。生产迁移链现行(v1→v6,_init 自注册),
# 冒烟经本口验证链式升级。
func register_migration(from_version: int, fn: Callable) -> void:
_migrations[from_version] = fn
# ---- 读盘(进档/注入时调用;启动态不读——_ready 已钉死不隐式激活)----
# 返回 true = 已就绪可用(含新档/损坏兜底/迁移成功);false = 高版本拒读(默认数据运行、禁写盘)。
# 损坏兜底:原文件改名 <path>.corrupt.bak(已存在则覆盖)+ push_warning + 默认数据继续——不静默丢档。
func load_from_disk() -> bool:
_read_only = false
_active_slot = _slot_index_of(path)
_data = _default_data()
load_from_disk()
# 测试隔离注入口(契约 7 同源——槽文件名模板整体换注,探针/单测经此隔离到自命名空间,
# 禁各自拼槽文件名;换注后回无激活态,槽原语全部落注入命名空间)
func attach_slot_template(template: String) -> void:
_slot_template = template
_path = ""
_active_slot = 0
_read_only = false
_data = _default_data()
# ---- 三存档位承载(卡①:隐式单档 → 3 存档位;位数与文件名口径=SLOT_COUNT/SLOT_PATH_TEMPLATE)----
# 槽文件路径唯一派生口(模板单点——禁第二处拼名)
func slot_path(slot: int) -> String:
return _slot_template % slot
# 激活槽号查询(0 = 无激活态——启动态钉死后 boot 需经选档显式进档/新档,卡③ Q1 甲)
func active_slot() -> int:
return _active_slot
# 写盘目标在位与否(激活槽或注入路径任一;无激活态一切写盘路径拒绝——sv3 判定口)
func has_write_target() -> bool:
return _path != ""
# 进档原语(sv5,契约 6/8):激活槽 N+读盘,复用 load_from_disk 迁移/兜底链按槽成立。
# 高版本拒读槽:激活不成立、恢复切换前三态(路径/激活号/禁写位/数据)——防误写他槽;
# 损坏槽:兜底链接管(.corrupt.bak 改名+空态继续,激活成立)。
func activate_slot(slot: int) -> bool:
if slot < 1 or slot > SLOT_COUNT:
return false
var prev_path := _path
var prev_slot := _active_slot
var prev_ro := _read_only
var prev_data := _data
_path = slot_path(slot)
_active_slot = slot
_read_only = false
_data = _default_data()
if not load_from_disk():
_path = prev_path
_active_slot = prev_slot
_read_only = prev_ro
_data = prev_data
return false
return true
# 新档原语(sv4):空槽创建=合法白板态(_default_data 全零可玩纪律),创建即激活并落盘。
# 生产路径只在空槽调用(有档槽开新局=先删档回空再新档,卡③ 契约 4);写盘失败回退切换前三态。
func create_slot(slot: int) -> bool:
if slot < 1 or slot > SLOT_COUNT:
return false
var prev_path := _path
var prev_slot := _active_slot
var prev_ro := _read_only
var prev_data := _data
_path = slot_path(slot)
_active_slot = slot
_read_only = false
_data = _default_data()
if not write_to_disk():
_path = prev_path
_active_slot = prev_slot
_read_only = prev_ro
_data = prev_data
return false
return true
# 删档原语(sv6,契约 8):该槽文件删除+盘面即时生效(卡内工程规定,挪用现役断点覆盖流语义)
# +无 .tmp 残留;删激活槽时激活指针连清除(内存回默认空态,该槽不再可写——激活态生命周期终点)。
# .corrupt.bak 损坏备份不追删(用户数据留盘可考;同槽再损坏时兜底流按槽覆盖同名备份)。
# 删不存在文件的空槽=幂等成功;越界槽号拒绝。
func delete_slot(slot: int) -> bool:
if slot < 1 or slot > SLOT_COUNT:
return false
var p := slot_path(slot)
if FileAccess.file_exists(p) and not _remove_file(p):
push_error("[存档序列化] 删档失败(文件删除被拒):%s" % p)
return false
var tmp := p + ".tmp"
if FileAccess.file_exists(tmp):
_remove_file(tmp) # 残留清偿尽力而为(主文件已删即达成空槽语义)
if _active_slot == slot:
_path = ""
_active_slot = 0
_data = _default_data()
return true
# 摘要三项按槽窥视读器(sv7/sv8,契约 5/9):钥石余额/冒险成功局数/冒险总局数。
# 只读零副作用:不激活、不写盘、不触发 _corrupt_fallback 改名(.corrupt.bak 只在正式进档路径产生)。
# 返回 {"readable", "has_file", "keystone", "cleared", "total"}——readable=true=可读槽(空槽=零值合法态,
# .tmp 残留按空槽——主文件不在即空);readable=false=不可读槽(损坏/高版本/缺版本字段/打开失败,
# 三值无意义——卡③ Q2 裁甲:UI 按空槽呈现,进档时走正式 load 兜底/拒读链);
# has_file=主文件在盘与否(空/有档操作集二分判定来处——卡③ T0 对齐结论;非摘要第四项,零 UI 语义)。
# 迁移链在此只读推演(生产迁移 Callable 为纯函数,零盘面副作用)。
func peek_slot_summary(slot: int) -> Dictionary:
if slot < 1 or slot > SLOT_COUNT:
return {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} # 越界=非槽位,非空槽
return _peek_summary_at(slot_path(slot))
# 窥视注入口(契约 7 隔离面同款——测试/探针经路径注入隔离,禁各自拼槽文件名)
func peek_summary_at(path: String) -> Dictionary:
return _peek_summary_at(path)
func _peek_summary_at(path: String) -> Dictionary:
var out := {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0}
if not FileAccess.file_exists(path):
out["readable"] = true # 空槽=可读零值合法态
return out
out["has_file"] = true # 主文件在盘(可读性与占档判定分离——白板档与空槽自此可区分)
var f := FileAccess.open(path, FileAccess.READ)
if not FileAccess.file_exists(slots._path):
_data = _default_data() # 新档合法态
return true
var f := FileAccess.open(slots._path, FileAccess.READ)
if f == null:
return out
return _corrupt_fallback("读打开失败(%s)" % error_string(FileAccess.get_open_error()))
return _load_parsed(f)
## 读盘解析域(load_from_disk 主体段提取——解析/版本/迁移/规整;corrupt_fallback 调用点随段迁移,副作用序逐字)
func _load_parsed(f: FileAccess) -> bool:
var text := f.get_as_text()
f.close()
var parsed = JSON.parse_string(text)
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("version"):
return out
if typeof(parsed) != TYPE_DICTIONARY:
return _corrupt_fallback("JSON 解析失败或顶层结构出域(非对象)")
if not parsed.has("version"):
return _corrupt_fallback("缺版本字段")
var v := int(parsed["version"])
if v > SAVE_FORMAT_VERSION:
return out
while v < SAVE_FORMAT_VERSION:
if not _migrations.has(v):
return out
var stepped = _migrations[v].call(parsed)
if typeof(stepped) != TYPE_DICTIONARY:
return out
parsed = stepped
v = int(parsed.get("version", v + 1))
var norm := _normalize(parsed)
out["readable"] = true
out["keystone"] = int(norm["meta"].get("keystone_balance", 0))
out["cleared"] = int(norm["meta"].get("adventure_cleared", 0))
out["total"] = int(norm["meta"].get("adventure_total", 0))
return out
_read_only = true
_data = _default_data()
push_error("[存档序列化] 档案版本 %d 高于当前 %d——拒读禁写(保护未来档不被覆写,内存默认新档运行)" % [v, SAVE_FORMAT_VERSION])
return false
var fail := []
var migrated = _peek_migrated(parsed, fail)
if migrated == null:
return _corrupt_fallback(fail[0])
_data = _normalize(migrated)
# 老档迁移(关卡节点图生成 v0.4 布尔位——缺字段 = 已有游玩史,不重放教学脚本)
if not _data["meta"].has("first_adventure_done"):
_data["meta"]["first_adventure_done"] = true
return true
# 注入路径的槽号反查(命中槽派生名返回槽号,否则 0——激活号记账用,写盘判定以 _path 非空为准)
func _slot_index_of(path: String) -> int:
for i in range(1, SLOT_COUNT + 1):
if path == slot_path(i):
return i
return 0
# ---- 写盘(tmp + rename 原子写——写一半断电不留烂档)----
# 无激活态一律拒绝并返回 false(sv3:启动态不隐式激活,盘面零触碰);
# 高版本拒读态一律拒绝(契约 4 禁写);写失败返回 false。
func write_to_disk() -> bool:
if slots._path == "":
push_error("[存档序列化] 写盘拒绝——无激活存档位(启动态不隐式激活;经选档进档/新档后落盘)")
return false
if _read_only:
push_error("[存档序列化] 写盘拒绝——档案版本高于当前(拒读禁写态)")
return false
var tmp := slots._path + ".tmp"
var f := FileAccess.open(tmp, FileAccess.WRITE)
if f == null:
push_error("[存档序列化] 临时文件打开失败:%s(%s)" % [tmp, error_string(FileAccess.get_open_error())])
return false
f.store_string(JSON.stringify(_data, "\t", true))
f.close()
if not _rename_overwrite(tmp, slots._path):
push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, slots._path])
return false
return true
# ---- 冒险断点域(停点粒度续玩快照;内容装配归 AdventureRunner,本类照单承载)----
# 空字典 = 无断点合法态。值拷贝进出(禁碰内部结构契约同款)——快照写入后调用方再改不渗入存档态。
func set_breakpoint(bp: Dictionary) -> void:
_data["breakpoint"] = bp.duplicate(true)
func clear_breakpoint() -> void:
_data["breakpoint"] = {}
func has_breakpoint() -> bool:
return not (_data.get("breakpoint", {}) as Dictionary).is_empty()
func breakpoint_snapshot() -> Dictionary:
return (_data.get("breakpoint", {}) as Dictionary).duplicate(true)
# ---- 私有:迁移链 / 损坏兜底 / 规整 / 文件原语 ----
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
func _migrate_v1_v2(data: Dictionary) -> Dictionary:
if not data.has("breakpoint"):
@@ -266,6 +195,53 @@ func _migrate_v6_v7(data: Dictionary) -> Dictionary:
return data
# 迁移循环提取(load/peek 两调用方同构共享):成功=迁移完成 Dictionary;失败=null
# (原因经 fail 数组带出——load 侧落 corrupt 兜底文案、peek 侧保持「返回未填充 out」逐字)
func _peek_migrated(parsed: Variant, fail: Array) -> Variant:
var v := int(parsed["version"])
while v < SAVE_FORMAT_VERSION:
if not _migrations.has(v):
fail.append("迁移链缺步骤 %d→%d" % [v, v + 1])
return null
var stepped = _migrations[v].call(parsed)
if typeof(stepped) != TYPE_DICTIONARY:
fail.append("迁移步骤 %d→%d 执行失败" % [v, v + 1])
return null
parsed = stepped
v = int(parsed.get("version", v + 1))
return parsed
## 摘要窥视内核(只读推演单点——SlotStore 两 peek 方法经槽路径派生委托至此;
## 迁移链在此只读推演——生产迁移 Callable 为纯函数,零盘面副作用)
func _peek_summary_at(path: String) -> Dictionary:
var out := {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0}
if not FileAccess.file_exists(path):
out["readable"] = true # 空槽=可读零值合法态
return out
out["has_file"] = true # 主文件在盘(可读性与占档判定分离——白板档与空槽自此可区分)
var f := FileAccess.open(path, FileAccess.READ)
if f == null:
return out
var text := f.get_as_text()
f.close()
var parsed = JSON.parse_string(text)
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("version"):
return out
if int(parsed["version"]) > SAVE_FORMAT_VERSION:
return out
var fail := []
var migrated = _peek_migrated(parsed, fail)
if migrated == null:
return out
var norm := _normalize(migrated)
out["readable"] = true
out["keystone"] = int(norm["meta"].get("keystone_balance", 0))
out["cleared"] = int(norm["meta"].get("adventure_cleared", 0))
out["total"] = int(norm["meta"].get("adventure_total", 0))
return out
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
func _default_data() -> Dictionary[String, Variant]:
@@ -299,368 +275,15 @@ func _default_data() -> Dictionary[String, Variant]:
}
# ---- 首次冒险布尔位(关卡节点图生成 v1.1 确定性承载)----
# false = 本档尚无游玩史 → 首局走固定种子 42 + 参数档生成图(教学与存档复现的固定图由此承载,无兜底模板);
# 置位只在冒险收束挂点(MetaProgression.settle_adventure)。
# 老档迁移:缺字段 = 该档已有游玩史 → true(不重放首局固定图)。
# 原地重置默认态(SlotStore 槽位原语的协作口——激活/建删/换注回默认数据)
func _reset_to_default() -> void:
_data = _default_data()
func is_first_adventure_done() -> bool:
return bool(_data["meta"].get("first_adventure_done", true))
func mark_first_adventure_done() -> void:
_data["meta"]["first_adventure_done"] = true
# ---- 读盘(进档/注入时调用;启动态不读——_ready 已钉死不隐式激活)----
# 返回 true = 已就绪可用(含新档/损坏兜底/迁移成功);false = 高版本拒读(默认数据运行、禁写盘)。
# 损坏兜底:原文件改名 <path>.corrupt.bak(已存在则覆盖)+ push_warning + 默认数据继续——不静默丢档。
func load_from_disk() -> bool:
_read_only = false
if not FileAccess.file_exists(_path):
_data = _default_data() # 新档合法态
return true
var f := FileAccess.open(_path, FileAccess.READ)
if f == null:
return _corrupt_fallback("读打开失败(%s)" % error_string(FileAccess.get_open_error()))
var text := f.get_as_text()
f.close()
var parsed = JSON.parse_string(text)
if typeof(parsed) != TYPE_DICTIONARY:
return _corrupt_fallback("JSON 解析失败或顶层结构出域(非对象)")
if not parsed.has("version"):
return _corrupt_fallback("缺版本字段")
var v := int(parsed["version"])
if v > SAVE_FORMAT_VERSION:
_read_only = true
_data = _default_data()
push_error("[存档序列化] 档案版本 %d 高于当前 %d——拒读禁写(保护未来档不被覆写,内存默认新档运行)" % [v, SAVE_FORMAT_VERSION])
return false
while v < SAVE_FORMAT_VERSION:
if not _migrations.has(v):
return _corrupt_fallback("迁移链缺步骤 %d→%d" % [v, v + 1])
var stepped = _migrations[v].call(parsed)
if typeof(stepped) != TYPE_DICTIONARY:
return _corrupt_fallback("迁移步骤 %d→%d 执行失败" % [v, v + 1])
parsed = stepped
v = int(parsed.get("version", v + 1))
_data = _normalize(parsed)
# 老档迁移(关卡节点图生成 v0.4 布尔位——缺字段 = 已有游玩史,不重放教学脚本)
if not _data["meta"].has("first_adventure_done"):
_data["meta"]["first_adventure_done"] = true
return true
# ---- 写盘(tmp + rename 原子写——写一半断电不留烂档)----
# 无激活态一律拒绝并返回 false(sv3:启动态不隐式激活,盘面零触碰);
# 高版本拒读态一律拒绝(契约 4 禁写);写失败返回 false。
func write_to_disk() -> bool:
if _path == "":
push_error("[存档序列化] 写盘拒绝——无激活存档位(启动态不隐式激活;经选档进档/新档后落盘)")
return false
if _read_only:
push_error("[存档序列化] 写盘拒绝——档案版本高于当前(拒读禁写态)")
return false
var tmp := _path + ".tmp"
var f := FileAccess.open(tmp, FileAccess.WRITE)
if f == null:
push_error("[存档序列化] 临时文件打开失败:%s(%s)" % [tmp, error_string(FileAccess.get_open_error())])
return false
f.store_string(JSON.stringify(_data, "\t", true))
f.close()
if not _rename_overwrite(tmp, _path):
push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, _path])
return false
return true
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
# Dictionary → Dictionary,须写回 version = from_version + 1。生产迁移链现行(v1→v6,_init 自注册),
# 冒烟经本口验证链式升级。
func register_migration(from_version: int, fn: Callable) -> void:
_migrations[from_version] = fn
# ---- 类型化域访问器(消费面——只经访问器操作,禁碰内部结构)----
func keystone_balance() -> int:
return int(_data["meta"]["keystone_balance"])
func add_keystone(n: int) -> void:
_data["meta"]["keystone_balance"] = keystone_balance() + n
# 钥石消费唯一口:余额不足拒绝且零副作用(钥石只增不减——消费除外,元进度验收判据)
func spend_keystone(n: int) -> bool:
if keystone_balance() < n:
return false
_data["meta"]["keystone_balance"] = keystone_balance() - n
return true
func item_unlocked(item_id: String) -> bool:
return _data["meta"]["item_unlocks"].has(item_id)
func unlock_item(item_id: String) -> void:
_data["meta"]["item_unlocks"][item_id] = true
func clear_count(level_index: int) -> int:
return int(_data["meta"]["level_clear_counts"].get(str(level_index), 0))
func boss_first_kill_marked(level_index: int) -> bool:
return _data["meta"]["boss_first_kill"].has(str(level_index))
# ---- 同调位账本域(里程碑谓词表计数源 + 装配关系 + Y 升级态)----
# Boss 击杀总数(含重复——底稿裁决 4 计数源;收束入账 = apply_adventure_summary 逐关卡完成关次 +1,
# Boss 恒唯一终点故 levels_cleared 即本次冒险 Boss 击杀数)
func boss_kill_count() -> int:
return int(_data["meta"].get("boss_kill_count", 0))
# ---- 生涯终身计数域(跨局汇总页数据源;只读面经访问器,
# 写入唯一经 apply_adventure_summary 收束口,禁第二写入点)----
# 冒险总局数(口径=开局即计:三结局 completed/failed/abandoned 同经收束恰计一次,含放弃与失败)
func adventure_total() -> int:
return int(_data["meta"].get("adventure_total", 0))
# 冒险成功局数(口径=三关全通才计 1——判定字段 summary.cleared,adventure_runner「completed」
# 唯一出口=末关的关卡完成)
func adventure_cleared() -> int:
return int(_data["meta"].get("adventure_cleared", 0))
# 真视类工具累计使用次数(信标网络解锁行为条件;写入唯一经 add_vision_used
# 冒险收束聚合入账,受理计数点=battle 层经冒险层聚合)
func vision_used() -> int:
return int(_data["meta"].get("vision_used", 0))
func add_vision_used(n: int) -> void:
_data["meta"]["vision_used"] = maxi(0, vision_used() + maxi(0, n))
# ---- 天赋树域(MetaProgression 树段读写面)----
func tree_nodes() -> Dictionary:
return (_data["meta"].get("tree_nodes", {}) as Dictionary).duplicate()
func tree_level(node_id: String) -> int:
return int((_data["meta"].get("tree_nodes", {}) as Dictionary).get(node_id, 0))
func set_tree_level(node_id: String, level: int) -> void:
var tn: Dictionary[Variant, Variant] = _data["meta"].get("tree_nodes", {}) as Dictionary
tn[node_id] = maxi(0, level)
_data["meta"]["tree_nodes"] = tn
func fork_sides() -> Dictionary:
return (_data["meta"].get("fork_sides", {}) as Dictionary).duplicate()
func fork_side(group: String) -> String:
return String((_data["meta"].get("fork_sides", {}) as Dictionary).get(group, ""))
func set_fork_side(group: String, side: String) -> void:
var fs: Dictionary[Variant, Variant] = _data["meta"].get("fork_sides", {}) as Dictionary
fs[group] = side
_data["meta"]["fork_sides"] = fs
# ---- 锻造工坊域 forge_routes(MetaProgression 锻造写口读写面;无特效域)----
# 解锁态不落档(派生):初始持有=表 initial_owned、练满前一档=levels[prereq]≥cap——判定单点在
# MetaProgression.forge_route_unlocked(读表+读档);本类只承载直购清单 unlocked 与选中标记。
# 域结构:{levels:{六路线 id→等级}, unlocked:[直购路线 id], weapon:当前武器路线 id, armor:当前甲路线 id}
func _forge_domain() -> Dictionary:
return _data["meta"].get("forge_routes", {}) as Dictionary
func forge_routes_snapshot() -> Dictionary:
return _forge_domain().duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
func forge_route_level(route_id: String) -> int:
var lv: Dictionary[Variant, Variant] = _forge_domain().get("levels", {}) as Dictionary
return int(lv.get(route_id, 0))
func set_forge_route_level(route_id: String, level: int) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
var lv: Dictionary[Variant, Variant] = dom.get("levels", {}) as Dictionary
lv[route_id] = maxi(0, level)
dom["levels"] = lv
_data["meta"]["forge_routes"] = dom
func forge_unlocked_purchases() -> Array:
return (_forge_domain().get("unlocked", []) as Array).duplicate()
func add_forge_unlock(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
var un: Array[Variant] = dom.get("unlocked", []) as Array
if not un.has(route_id):
un.append(route_id)
dom["unlocked"] = un
_data["meta"]["forge_routes"] = dom
func forge_weapon() -> String:
return String(_forge_domain().get("weapon", "sword"))
func set_forge_weapon(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
dom["weapon"] = route_id
_data["meta"]["forge_routes"] = dom
func forge_armor() -> String:
return String(_forge_domain().get("armor", "light"))
func set_forge_armor(route_id: String) -> void:
var dom: Dictionary[Variant, Variant] = _forge_domain()
dom["armor"] = route_id
_data["meta"]["forge_routes"] = dom
# 装配关系(同调目录 item_id 有序数组——装配序即呈现序;跨冒险持久,冒险中途不可换装〔底稿 §8.5〕)。
# 局外进度事实(元进度域同族),非契约 2 反面清单的「局内道具库存」——口径见 存档序列化.md 契约 2。
func attunement_equipped() -> Array:
return (_data["meta"].get("attunement_equipped", []) as Array).duplicate()
func set_attunement_equipped(item_ids: Array) -> void:
var out: Array[Variant] = []
for v in item_ids:
out.append(String(v))
_data["meta"]["attunement_equipped"] = out
# Y 刷新升级购买等级(0 = 未购 → 每冒险刷新次数 0;等级行值归营地升级行族)
func attune_refresh_level() -> int:
return int(_data["meta"].get("attune_refresh_level", 0))
func set_attune_refresh_level(n: int) -> void:
_data["meta"]["attune_refresh_level"] = maxi(0, n)
# 同调位计数源组装(Attunement 服务取数口——clear_count = 逐关完成次数合计〔含重复关,底稿 §2.1 口径〕;
# clear_count(level_index) 逐关访问器零改动复用)
func recent_summaries() -> Array:
return _data["meta"]["recent_summaries"].duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
# 钥石入账(summary.keystone_total——逐杀掉落已在冒险内计入,boss_drops 参数为
# 摘要归档副本不重复入账)+ 逐关卡完成关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
# (同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
# keystone_drops 副本——复盘可归因)+ 生涯终身计数自增(总局数开局即计:
# 三结局同经本口恰计一次;冒险成功局数按 summary.cleared 判定三关全通才计 1。终身计数写入
# 单口=本函数,禁第二写入点)。
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
_data["meta"]["adventure_total"] = adventure_total() + 1
if bool(summary.get("cleared", false)):
_data["meta"]["adventure_cleared"] = adventure_cleared() + 1
add_keystone(int(summary.get("keystone_total", 0)))
var cleared := int(summary.get("levels_cleared", 0))
for li in range(1, cleared + 1):
var key := str(li)
_data["meta"]["level_clear_counts"][key] = int(_data["meta"]["level_clear_counts"].get(key, 0)) + 1
_data["meta"]["boss_first_kill"][key] = true
_data["meta"]["boss_kill_count"] = boss_kill_count() + cleared # Boss 恒唯一终点:levels_cleared 即本次冒险 Boss 击杀数(含重复)
var entry: Dictionary[Variant, Variant] = summary.duplicate(true)
entry["boss_drops"] = boss_drops.duplicate(true)
var arr: Array[Variant] = _data["meta"]["recent_summaries"]
arr.push_front(entry)
while arr.size() > SUMMARY_CAP:
arr.pop_back()
# ---- 冒险断点域(停点粒度续玩快照;内容装配归 AdventureRunner,本类照单承载)----
# 空字典 = 无断点合法态。值拷贝进出(禁碰内部结构契约同款)——快照写入后调用方再改不渗入存档态。
func set_breakpoint(bp: Dictionary) -> void:
_data["breakpoint"] = bp.duplicate(true)
func clear_breakpoint() -> void:
_data["breakpoint"] = {}
func has_breakpoint() -> bool:
return not (_data.get("breakpoint", {}) as Dictionary).is_empty()
func breakpoint_snapshot() -> Dictionary:
return (_data.get("breakpoint", {}) as Dictionary).duplicate(true)
# ---- 私有:损坏兜底与文件原语 ----
# 旧隐式单档一次性迁移(Q1 甲,sv12):src 字节级原样搬运到 dst(零解析零改造——损坏/高版本
# 旧档字节原样落槽 1,读取时由正式进档路径的兜底/拒读链接管,不静默丢数据);
# dst 已存在不重迁(幂等判据——判别锚④:去判据则重复迁移覆盖槽 1);迁移成功后 src 改名
# bak 留痕(不删——处置字节留痕,防再次隐式读写)。任一步失败:源保留原地,下次启动重试。
# tmp+rename 原子形态与 write_to_disk 同纪律。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。
func _migrate_legacy_save(src: String, dst: String, bak: String) -> void:
if not FileAccess.file_exists(src):
return # 执行前置:旧档不存在=无操作
if FileAccess.file_exists(dst):
return # 幂等判据:槽 1 已存在不重迁
var tmp := dst + ".tmp"
if not _copy_file(src, tmp):
push_warning("[存档序列化] 旧单档迁移失败(源保留,下次启动重试):%s → %s" % [src, tmp])
return
if not _rename_overwrite(tmp, dst):
push_warning("[存档序列化] 旧单档迁移改名失败(tmp 保留待查,源保留重试):%s → %s" % [tmp, dst])
return
_rename_overwrite(src, bak)
push_warning("[存档序列化] 旧单档已迁入 %s,原件留痕于 %s" % [dst, bak])
# 字节级文件拷贝(迁移专用——buffer 直拷,零解析零改造)
func _copy_file(from: String, to: String) -> bool:
var src := FileAccess.open(from, FileAccess.READ)
if src == null:
return false
var dst := FileAccess.open(to, FileAccess.WRITE)
if dst == null:
src.close()
return false
while not src.eof_reached():
var chunk := src.get_buffer(65536)
if chunk.size() > 0:
dst.store_buffer(chunk)
src.close()
dst.close()
return true
# 文件删除(删档原语专用——呈现层禁 DirAccess 直删,承载面单点)
func _remove_file(p: String) -> bool:
var dir := DirAccess.open(p.get_base_dir())
if dir == null:
return false
return dir.remove(p.get_file()) == OK
# 损坏兜底:坏档改名 .corrupt.bak(已存在则覆盖)→ 默认数据继续。不静默丢档(警告留痕)、不 crash。
func _corrupt_fallback(reason: String) -> bool:
var bak := _path + ".corrupt.bak"
if not _rename_overwrite(_path, bak):
var bak := slots._path + ".corrupt.bak"
if not _rename_overwrite(slots._path, bak):
push_warning("[存档序列化] 坏档备份改名失败(原文件保留原地):%s——%s" % [bak, reason])
else:
push_warning("[存档序列化] 检测到损坏档案,已备份为 %s——%s(用默认新档继续)" % [bak, reason])
@@ -689,7 +312,8 @@ func _normalize(data: Dictionary) -> Dictionary[String, Variant]:
return merged
# 覆盖式改名(Windows 原生 rename 目标已存在时会失败——先删后改,显式覆盖语义)
# 覆盖式改名(Windows 原生 rename 目标已存在时会失败——先删后改,显式覆盖语义;IO 原语单点——
# SlotStore 迁移流经本件调用)
func _rename_overwrite(from: String, to: String) -> bool:
var dir := DirAccess.open(from.get_base_dir())
if dir == null:
@@ -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
+4 -4
View File
@@ -26,7 +26,7 @@ func run(ri: int) -> void:
# 同一次 _settle 内,facts 已定。
var round_safe := 0
var round_mine := false
for evt in oc._facts_reveals:
for evt in oc._facts.reveals:
if evt.kind == "safe":
round_safe += 1
elif evt.kind == "mine":
@@ -53,7 +53,7 @@ func _hero_strike(ri: int) -> void:
var hero_target = oc._enemy_system.default_target()
if hero_alive and hero_target != null:
var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击",
{"channel": "combat", "base_value": oc._hero_atk, "target_rule": DamageController.TARGET_FRONT_ALIVE,
{"channel": "combat", "base_value": oc._hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE,
"ri": ri, "origin_cell": Vector2i(-1, -1),
"attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))})
oc._damage.submit(ht)
@@ -78,7 +78,7 @@ func _enemy_actions(ri: int) -> void:
if int(oc._enemy_system.hero_unit().hp) <= 0:
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
var skill_id := oc._enemy_system.pending_action(u)
var srow: Dictionary[Variant, Variant] = oc.db.skill_row(skill_id)
var srow: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(skill_id)
if srow.is_empty():
oc._fatal("敌实例待发动作悬空: %s" % skill_id)
continue
@@ -97,7 +97,7 @@ func _enemy_actions(ri: int) -> void:
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
var rider := 0
if srow.has("rider_mult"):
rider = maxi(int(oc.db.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
rider = maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
var et := oc._damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action",
"%s %s" % [u.display_name, String(srow.display_name)],
{"channel": "combat", "base_value": baseline, "target_rule": DamageController.TARGET_HERO,
+73 -47
View File
@@ -6,7 +6,8 @@ extends RefCounted
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_terrain_damage_reduce/_seal_hit_effects 等单面函数);
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken
# 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数);
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
@@ -32,9 +33,30 @@ func run(ri: int) -> void:
_round_end_apply(ri)
# 成功刷新事实发射+施加(迭代 46 卡A T4——BattleRefreshFlow 装新板后单点调用,每成功刷新恰一次;
# 拒绝刷新零事实〔C2〕。命中消费三面:counter_add→既有单点落账〔蓄力周转/石层缓冲/坑道小憩 计数〕;
# grant_gp→战斗内缓冲 escrow 入账+fx_grant_gp 行〔矿道红利——不绑清图奖励,C7〕;
# open_ticket channel=blast→崩壁冲击群伤单延迟入队〔案C 同队列,战斗段末按存活目标结账〕)
func apply_board_refreshed(ri: int) -> void:
var br_hits := fx_host.fx_emit("board_refreshed", {"round": ri})
for h in br_hits:
if String(h.get("kind", "")) == "counter_add":
_apply_counter_hit(h)
for h in br_hits:
if String(h.get("kind", "")) == "grant_gp":
reward_grant.escrow += int(h.get("value", 0))
oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow})
for h in br_hits:
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
var bb_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击",
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
"ri": ri, "origin_cell": Vector2i(-1, -1)})
reward_grant.defer_ticket(bb_t)
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
func _reveal_batch(ri: int) -> void:
for evt in oc._facts_reveals:
for evt in oc._facts.reveals:
if not bool(evt.get("resource", false)):
continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②)
var snap = evt.get("reward_snapshot")
@@ -61,7 +83,7 @@ func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
if is_new_type:
fx_host._fx_new_resource_types[ctype] = true
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),
"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,
@@ -91,8 +113,8 @@ func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void:
if String(h.get("kind", "")) == "grant_material":
_apply_material_hit(h, evt.cell, ri)
var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell")
if not spell_hits.is_empty() and not oc._feat_first_dig_used:
oc._feat_first_dig_used = true
if not spell_hits.is_empty() and not oc._facts.feat_first_dig_used:
oc._facts.feat_first_dig_used = true
var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢",
{"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"})
@@ -147,7 +169,7 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
if fx_host._fx_bonus_delta.has(axis):
fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0))
"blast_scaled":
var blast_v := maxi(int(oc.db.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
var blast_v := maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发",
{"channel": "board", "base_value": blast_v, "target_rule": DamageController.TARGET_ALL_ALIVE,
"ri": ri, "origin_cell": cell})
@@ -160,50 +182,35 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
func _terrain_batch(ri: int) -> void:
for evt in oc._facts_reveals:
for evt in oc._facts.reveals:
var vids: Array[Variant] = evt.get("variant_ids", [])
for vid in vids:
var vrow: Dictionary[Variant, Variant] = oc.db.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":
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
# damage_taken 发射在单据开立前:damage_reduce〔背水——hp_ratio when
# 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化)
# damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken:
# 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
# hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
var dt_amount := int(vrow.get("effect_value", 0))
var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount,
"source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid),
"hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))})
dt_amount = _terrain_damage_reduce(dt_hits, dt_amount)
var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
dt_amount = int(dt_res["total"])
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid)}})
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}})
reward_grant.submit(vt, false)
for h in dt_hits:
for h in (dt_res["hits"] as Array):
if String(h.get("kind", "")) == "grant_material":
_apply_material_hit(h, evt.cell, ri)
# damage_taken 施加面(damage_reduce 减量收集〔背水——定值〕/ac_bonus〔石肤〕/counter_add〔保险丝/石肤〕;
# 返回减后 amount 交开单消费)
func _terrain_damage_reduce(dt_hits: Array, dt_amount: int) -> int:
for h in dt_hits:
match String(h.get("kind", "")):
"damage_reduce":
dt_amount = maxi(0, dt_amount - int(h.get("value", 0)))
"ac_bonus":
fx_host.apply_ac_bonus(h)
"counter_add":
_apply_counter_hit(h)
return dt_amount
# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
# 伤害单据案C 暂存战斗段末兑付。
func _seal_batch(ri: int) -> void:
for evt in oc._facts_seals:
for evt in oc._facts.seals:
_judge_seal(evt, ri)
_seal_strike_batch(ri)
@@ -213,7 +220,7 @@ func _seal_batch(ri: int) -> void:
func _seal_strike_batch(ri: int) -> void:
var fs_hits := 0
var fs_misses := 0
for fevt in oc._facts_seals:
for fevt in oc._facts.seals:
if bool(fevt.get("hit", false)):
fs_hits += 1
else:
@@ -223,7 +230,7 @@ func _seal_strike_batch(ri: int) -> void:
var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses)
if strike.is_empty():
return
for fevt in oc._facts_seals:
for fevt in oc._facts.seals:
if not bool(fevt.get("hit", false)):
continue
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:
var re_fx := fx_host.fx_emit("round_end", {"round": ri})
_round_end_blast_tickets(re_fx, ri)
_round_end_heal_value_counter(re_fx, ri) # 迭代 46 卡A T5——坑道小憩恢复兑付(先于清零行消费层值,声明序=heal 行在 counter set 行前)
for h in re_fx:
if String(h.get("kind", "")) == "counter_add":
_apply_counter_hit(h)
@@ -272,12 +280,30 @@ func _round_end_apply(ri: int) -> void:
var rap_pct: int = oc.attune_param_sum("rap_transfer_mult")
oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"),
rap_pct if rap_pct > 0 else 100)
if oc._facts_path == "cleared":
reward_grant.grant_clear_reward(ri, oc._board_from_midround_refresh, oc.perfect_sweep_earned())
if oc._facts.path == "cleared":
reward_grant.grant_clear_reward(ri, oc._facts.board_from_midround, oc.perfect_sweep_earned())
fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点,
# units.heal 封顶、满血发未生效行;层清零由同事件 counter_add set 行承载〔声明序在后〕——本面
# 先于 counter_add 遍历消费层值即保序。行源=成功刷新事实族(无棋盘格——fx_board_refresh 豁免类))
func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void:
for h in re_fx:
if String(h.get("kind", "")) != "heal" or String(h.get("value_counter", "")) == "":
continue
var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", "")))
if layers <= 0:
continue
var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)):
oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))})
continue
var healed: int = oc._enemy_system.heal(hu, layers)
oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"})
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付)
func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
@@ -305,10 +331,10 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付)
func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
var cell: Vector2i = evt.cell
if oc._seal_consumed.has(cell):
if oc._facts.seal_consumed.has(cell):
return # 去重跳过;零明细行
oc._seal_consumed[cell] = true
oc._perfect_hit_true += 1
oc._facts.seal_consumed[cell] = true
oc._facts.mark_perfect_hit()
var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri,
"terrain_kind": oc._harmful_adjacent(cell)})
_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)
if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))):
types.append(String((n_snap as Dictionary).get("cell_type", "")))
if oc.treasure_inventory != null and not types.is_empty():
oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
if reward_grant.treasure_inventory != null and not types.is_empty():
reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types})
@@ -376,10 +402,10 @@ func _seal_preview_area(cell: Vector2i, ri: int) -> void:
# grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付)
func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
var cell: Vector2i = evt.cell
oc._perfect_hit_false += 1
if oc._seal_miss_seen.has(cell):
oc._facts.mark_perfect_miss()
if oc._facts.seal_miss_seen.has(cell):
return # 每格实例一次去重
oc._seal_miss_seen[cell] = true
oc._facts.seal_miss_seen[cell] = true
# 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错
# 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在
# 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;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":
# 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账
# _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 不再命中)
oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0,
{"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven})
var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else ""
{"effect_id": "insurance_used", "forgiven": oc._facts.perfect_forgiven})
var hl := "perfect_broken" if oc._facts.perfect_hit_false - oc._facts.perfect_forgiven == 1 else ""
oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {})
var snap = oc._reward_economy.grant(cell)
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
+17 -17
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 商店会话件:关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主。
## 钱包/主角账/装配集归宿主(GpWallet/GainJournal 共享引用经 bind 注入——GP 读写走钱包、购买加算走日记单点);
## Core 层零 UI 依赖;公开面由 LevelRunner 同签名委托承载(外部调用面零变化);
## Core 层零 UI 依赖;外部调用面=调用方经宿主 LevelRunner 公开成员 shop 直连(调用直连不留委托壳);
## legacy 模式零 RNG 零池消费(off 对照承载)。
class_name ShopSession
extends RefCounted
@@ -34,7 +34,7 @@ func bind(refs: Dictionary, hooks: Dictionary) -> void:
# 商店覆盖层数据源(只读投影——现价/已购档数在此现算,呈现层禁复算定价公式)
func shop_products() -> Array:
var out: Array[Dictionary] = []
for prow in db.shop_products():
for prow in db.run.shop.product_rows():
out.append({
"product_id": String(prow.get("product_id", "")),
"display_name": String(prow.get("display_name", "")),
@@ -49,7 +49,7 @@ func shop_products() -> Array:
# 现价 = base_price + price_level_bonus(level_index) + price_increment × 该商品已购档数
# (散折价:economy_spend 激活时商品标价 ×80%——见 _spend_discounted)
func current_price(prow: Dictionary) -> int:
return _spend_discounted(int(prow.get("base_price", 0)) + db.shop_price_level_bonus(_level_index()) \
return _spend_discounted(int(prow.get("base_price", 0)) + db.run.shop.price_level_bonus_at(_level_index()) \
+ int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", ""))))
@@ -58,7 +58,7 @@ func current_price(prow: Dictionary) -> int:
# + 池货架面 pool_price 同折〕——商店刷新价 shop_refresh_cost 与三选一付费刷新不折)
func _spend_discounted(price: int) -> int:
if _rules().has("economy_spend"):
return maxi(1, int(floor(float(price) * float(db.shop.spend_discount))))
return maxi(1, int(floor(float(price) * float(db.run.shop.spend_discount))))
return price
@@ -69,7 +69,7 @@ func purchased_tier(product_id: String) -> int:
# 购买(未知商品/属性键出域/余额不足拒绝——返回 false 零副作用):
# 出账 → 加算区 += flat_value → 购血同步当前生命(封顶新上限)→ 已购档数 +1
func buy_product(product_id: String) -> bool:
var prow: Dictionary[Variant, Variant] = db.shop_product_row(product_id)
var prow: Dictionary[Variant, Variant] = db.run.shop.product_row(product_id)
if prow.is_empty():
push_error("[关卡节点地图] 商店房未知商品: %s" % product_id)
return false
@@ -133,7 +133,7 @@ func leave() -> void:
func _draw_shelf() -> void:
shelf = []
var sellable: Array[Variant] = []
for row in db.shop_pool_rows():
for row in db.run.shop.pool_rows():
var shelf_kind := String(row.get("kind_id", ""))
if _pool_cap.is_valid() and not bool(_pool_cap.call(shelf_kind, "")):
continue # cap 满剔除(货架候选视图剔除;牌库本体不动)
@@ -184,14 +184,14 @@ func pool_stock(kind: String) -> int:
# A/b 表 knob provisional;核心现算,呈现层禁复算——契约 5)。relic 卡(保底占位——shop 词表外)
# 基价 provisional 15(表 knob relic_base_price——shop.tres 词表扩列归数值批)。
func pool_price(kind: String) -> int:
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
var row: Dictionary[Variant, Variant] = db.run.shop.pool_row(kind)
if row.is_empty():
if kind == "relic":
return _spend_discounted(int(ceil(float(db.shop.relic_base_price) * pow(db.shop_price_b(), float(maxi(0, _level_index() - 1))) * pow(db.shop_price_a(), float(visit_buys)))))
return _spend_discounted(int(ceil(float(db.run.shop.relic_base_price) * pow(db.run.shop.price_b, float(maxi(0, _level_index() - 1))) * pow(db.run.shop.price_a, float(visit_buys)))))
return 999999
var base := float(maxi(1, int(row.get("base_price", 1))))
var price_f: float = base * pow(db.shop_price_b(), float(maxi(0, _level_index() - 1))) \
* pow(db.shop_price_a(), float(visit_buys))
var price_f: float = base * pow(db.run.shop.price_b, float(maxi(0, _level_index() - 1))) \
* pow(db.run.shop.price_a, float(visit_buys))
return _spend_discounted(int(ceil(price_f)))
@@ -202,11 +202,11 @@ func shop_shelf() -> Array:
for tok in shelf:
var kind := String((tok as Dictionary).get("kind", ""))
var tier := String((tok as Dictionary).get("tier", ""))
var row: Dictionary[Variant, Variant] = db.shop_pool_row(kind)
var row: Dictionary[Variant, Variant] = db.run.shop.pool_row(kind)
var display := String(row.get("display_name", kind))
var value := int(db.adventure.curio_value(kind, tier))
var value := int(db.run.adventure.curio_value(kind, tier))
if row.is_empty() and kind == "relic":
var rrow: Dictionary[Variant, Variant] = db.feat_row(String(tok.get("feat_id", "")))
var rrow: Dictionary[Variant, Variant] = db.crossrun.feats.row_by_id(String(tok.get("feat_id", "")))
display = String(rrow.get("display_name", "特性"))
value = 0
out.append({
@@ -227,22 +227,22 @@ func shop_shelf() -> Array:
func shop_refresh_free_left() -> int:
# 刷新执照 shop_refresh_bonus 装配合计叠加(进店入账语义=读数即本店可用,
# 与基础 knob 同生命周期;未装配=基值)
return maxi(0, int(db.adventure.shop_refresh_free) + _attune_shop_refresh_bonus())
return maxi(0, int(db.run.adventure.shop_refresh_free) + _attune_shop_refresh_bonus())
# 刷新执照装配合计(装配集 kind 分发扫描;零装配=0)
func _attune_shop_refresh_bonus() -> int:
var total := 0
for iid_v in _attunement():
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(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("shop_refresh_bonus"):
total += int(arow.get("magnitude", 0))
return total
func shop_refresh_cost() -> int:
return int(ceil(float(db.adventure.shop_refresh_cost_base)
* pow(db.adventure.shop_refresh_cost_curve, float(paid_refreshes))))
return int(ceil(float(db.run.adventure.shop_refresh_cost_base)
* pow(db.run.adventure.shop_refresh_cost_curve, float(paid_refreshes))))
func shop_refresh() -> bool:
Loaded 100 of 315 files, more files were not shown because too many files have changed in this diff. Show more