feat(迭代27 T6+T7): 波五b 卫生件包+文档同步闸——对拍全同

T6 卫生件(P2 组收口):
- 死代码 13 函数删除(审计 16 处复核=ui_kit:486 实读现役不删,追认单#14)+FeatsEngine
  整文件退役(class_name 注销)
- 字符串 Callable×10 位→方法引用形态(编译期方法存在性校验;语法实证 METHOD_REF=true)
- 状态机 enum 化: adventure State(外部 String 契约 getter 词表逐位一致+外部纯读实证)/
  standee HeroState(register/set String 形参零变化+probe 面恒等)
- ConfigDB _fatal 退出归根(核心层去 quit/crash→_fatal_flag,决策归组合根 fail-fast 通道)
  +resolver 诊断/数据通道全面 Callable 化(ConfigDb↔Resolver 类型环断根)

T7 文档同步闸:
- 实现报告 design/qa/iter27-实现报告-2026-10-14.md(五波+闸表+追认单十五项+反哺点两条+目检五件)
- 06 迭代 27 条销号+GDD 反哺点两条新条;07 ㉖ 目检节+v1.0 版本行;roadmap ✅ 换代
- 审计报告本体按退役规则 git rm(吸收即死;修正计数由卡片承载);卡片来源行改指路

闸: 对拍三变体全同(第五跑)+冒烟 1081/0+run_all 27 套+probe click+render golden+static_scan 全绿
This commit is contained in:
DKChan committed 2026-10-15 01:41:37 +08:00
1 parent b605022001
commit eea345882b
24 files changed
+200 -309

No files matched your search

+13 -9
View File
@@ -54,7 +54,11 @@ signal wallet_changed(event: String, gp: int, extra: Dictionary)
var db: ConfigDb
var rng: RngServiceCore
var state := "idle" # idle / running / settling(冒险结构.md 三态)
enum State { IDLE, RUNNING, SETTLING } # 会话状态机(迭代 27 T6——裸字符串 enum 化:内部 enum,外部 String 契约 getter 词表逐位一致)
var _state: State = State.IDLE
var state: String: # 外部契约面(只读——外部零写点实证;"idle"/"running"/"settling" 零变化)
get:
return State.keys()[_state].to_lower()
var ledger := {"hero_hp": 0, "gp": 0} # 冒险账本(生命 + GP 钱包——经 start_level_external 注入 runner 共享)
var run_ledger := RunLedger.new() # 共享四本账(迭代 27 T3——P1-4 收口:attune_charges/vision_counter/adventure_counters/bonus 单类持账;裁点②甲)
var purchased := {} # product_id → 已购档数(跨关延续、冒险收束清零——递增定价输入)
@@ -153,7 +157,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
for r in feat_seed:
_feats.append(String(r))
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含调试浮窗 pct,与战斗内同源)
state = "running"
_state = State.RUNNING
return _start_current_level()
@@ -257,13 +261,13 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
migrated.append({"kind": String(c), "tier": db.adventure.TIER_COMMON})
_curio_pending = {"candidates": migrated}
var payload: Dictionary[Variant, Variant] = (bp_pending.get("payload", {}) as Dictionary).duplicate(true)
state = "running"
_state = State.RUNNING
curio_choice_presented.emit(payload)
return true
if not bp_result.is_empty():
_pending_next_level = true
_curio_result = bp_result.duplicate(true)
state = "running"
_state = State.RUNNING
curio_resolved.emit(_curio_result.duplicate(true))
return true
_resume_bp = bp.duplicate(true)
@@ -271,7 +275,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
var bp_charges = bp.get("attunement_charges", null)
if bp_charges is Dictionary:
run_ledger.attune_charges = (bp_charges as Dictionary).duplicate(true)
state = "running"
_state = State.RUNNING
return _start_current_level()
@@ -936,7 +940,7 @@ func _start_current_level() -> bool:
_runner.set_run_ledger(run_ledger) # 迭代 27 T3——共享四账单类下传(原四 setter/参数散传收口;run_ledger=四账类,ledger=关卡账沿用旧名)
_runner.sync_attune_inventory() # 迭代 11 第①批 T6 原语义——能力件骑库存面注入(随账下传同拍)
var ok: bool = _runner.start_level_external(level_index, ledger, purchased,
Callable(self, "apply_rest"), _base_override, true, template_pick, resume_state)
apply_rest, _base_override, true, template_pick, resume_state)
if ok:
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
_runner.set_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面)
@@ -945,7 +949,7 @@ func _start_current_level() -> bool:
if _shop_mode == "pool":
# 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动。
# 迭代 20 T2——cap 检查回调四参注入)
_runner.set_shop_pool(_curio_deck, Callable(self, "_consume_pool_purchase"), Callable(self, "_feat_ticket_consider"), Callable(self, "_curio_cap_ok"))
_runner.set_shop_pool(_curio_deck, _consume_pool_purchase, _feat_ticket_consider, _curio_cap_ok)
# 迭代 20 T4——凭证件三账注入(节点级/回合级一次性:注入即清零,未用即过期=寿命档语义)
_runner.set_voucher_grants(_pending_rap, _time_sand_queue, _pending_insurance)
_pending_rap = 0
@@ -1043,9 +1047,9 @@ func _settle(result: String) -> void:
if _save_manager != null:
_save_manager.clear_breakpoint() # 三结局清断点(迭代 8 卡①/T3——退出冒险=放弃流程+清除断点)
_save_manager.write_to_disk()
state = "settling"
_state = State.SETTLING
adventure_finished.emit(summary)
state = "idle"
_state = State.IDLE
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明)----
+3 -7
View File
@@ -108,7 +108,7 @@ var _fx: EffectEngine
func _fx_emit(event: String, payload: Dictionary) -> Array:
if _fx == null:
return []
return _fx.dispatch(event, payload, Callable(self, "_fx_fact"))
return _fx.dispatch(event, payload, _fx_fact)
# round_start 发射+施加(内容数据驱动扩展批 T3——grant_ap mode:queue=当轮入队,行动经济轮切换
@@ -219,10 +219,6 @@ func attune_has(item_id: String) -> bool:
# 装备件表值读取(charge_n 等——TreasureItemTable 行;未装配不读表。magnitude 读数面随 T4
# 秘宝订阅迁移退役——行值经 value_from 在 rebuild 定影,零运行期按件取值)
func _attune_row(item_id: String) -> Dictionary:
return db.treasure_item_row(item_id) if _attune_has(item_id) else {}
# 装配集参数合计(迭代 22 秘宝批 T3——常时参数面 kind 的装配集 Σmagnitude 读数口;
# kind 分发查询先例=_attune_refresh_x_total bonus_refresh。零装配=0)
func attune_param_sum(kind: String) -> int:
@@ -395,8 +391,8 @@ func begin_battle(enc: Dictionary) -> bool:
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
var ledger_ref = enc.get("ledger", null)
_ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面
_damage.fx_reveal_request = Callable(self, "_fx_enqueue_reveal")
_damage.fx_ac_request = Callable(self, "_apply_ac_bonus")
_damage.fx_reveal_request = _fx_enqueue_reveal
_damage.fx_ac_request = _apply_ac_bonus
var feat_ids: Array[Variant] = enc.get("feats", []) as Array
# 迭代 20 T3 换代(V14 旧实现摘除)——流派档位质变随重设计退役(奇物.md v1「不设流派标签、
# 不设档位机制」):FeatsEngine 求值与 rider_tier 注入拆除,散件单体效果=唯一形态
-4
View File
@@ -58,10 +58,6 @@ func board_variant_ids() -> Array[String]:
# 中和格变异(迭代 12 T6 净化工兵——伤害型地形格变普通格;🔴 R4 甲仅限伤害型的过滤在调用方)
func clear_variants(c: Vector2i) -> void:
variants.erase(c)
func neighbors8(c: Vector2i) -> Array:
var out: Array[Vector2i] = []
for dy in range(-1, 2):
+15 -20
View File
@@ -60,7 +60,8 @@ var _reload_gate := false # 数据配置 EC8——战斗结
func _init() -> void:
_resolver = ConfigDbResolver.new(self) # 协作类持门面引用(诊断/致命通道回呼)
_resolver = ConfigDbResolver.new(Callable(self, "push_diag"), Callable(self, "reset_warn_dedup"),
func() -> Variant: return board_gen.rows, func() -> Variant: return enemy.rows, func() -> Variant: return difficulty) # 协作类持门面引用(诊断/致命通道回呼)
func push_diag(severity: String, category: String, message: String) -> void:
@@ -71,20 +72,25 @@ func push_diag(severity: String, category: String, message: String) -> void:
push_error("[ConfigDB/%s] %s" % [category, message])
# 运行期数据致命(2026-09-18 用户拍板:越域记 Fatal 级日志后立即退出——数据配置通道 fail-fast 纪律补齐到查询期)
# 运行期数据致命(2026-09-18 用户拍板 fail-fast;迭代 27 T6——退出执行归根:核心层不再自携
# quit/crash,越域记 Fatal 级日志后置 _fatal_flag,进程退出决策归组合根(main._ready 表加载
# 失败已有同通道 fatal 屏+fail-fast 边界)——查询期消费方按返回值安全面走既有默认值回退)
func _fatal(category: String, message: String) -> void:
push_diag("fatal", category, message)
push_error("[ConfigDB/%s] FATAL: %s —— 数据配置致命,进程退出" % [category, message])
var main_loop := Engine.get_main_loop()
if main_loop is SceneTree:
main_loop.quit(1) # 帧末退出,退出码 1——调用方须立即 return,勿再消费返回值
else:
OS.crash("ConfigDB/%s fatal: %s" % [category, message])
push_error("[ConfigDB/%s] FATAL: %s —— 数据配置致命(退出决策归组合根)" % [category, message])
_fatal_flag = true
var _fatal_flag := false # 运行期致命旗(迭代 27 T6——退出归根承载;组合根/闸读面)
func is_fatal() -> bool:
return _fatal_flag
func reset_diagnostics() -> void:
_diagnostics.clear()
_resolver.reset_warn_dedup() # R1 告警去重注册表(协作类持钥——清空时点与拆分前一致)
_resolver._reset_warn.call() # R1 告警去重注册表(协作类持钥——清空时点与拆分前一致;通道 Callable 化随 T6)
func diagnostics() -> Array:
@@ -250,10 +256,6 @@ func level_count() -> int:
# ---- GP+商店房实施批(2026-09-21):ShopTable 消费面(行集解析单点——runner/战斗编排禁自扫表)----
# ── 秘宝访问器(迭代 7 T1——消费面一律走访问器,禁直读行集)──
# 活行集(非 retired 非 deferred)——投放校验/奖励节点池/呈现可用性的唯一取数口径
func treasure_active_rows() -> Array:
return treasure_items.active_rows() if treasure_items != null else []
func treasure_item_row(item_id: String) -> Dictionary:
return treasure_items.row_by_id(item_id) if treasure_items != null else {}
@@ -270,13 +272,6 @@ func treasure_attunement_rows() -> Array:
# 活行词表(掉落投放 effect_id 白名单——过渡渠道口径)
func treasure_live_ids() -> Dictionary:
var out := {}
for row in treasure_transitional_rows():
out[String(row.get("item_id", ""))] = true
return out
# ── 同调轴访问器已删除(迭代 21 T5——attunement_growth 谓词表随槽位收编退役:槽位数量=树同调位
# 节点点亮数,判定源=Attunement.slots;boss_kill_count/clear_count 计数域保留——非槽位硬消费在册)──
+20 -10
View File
@@ -11,12 +11,21 @@
class_name ConfigDbResolver
extends RefCounted
var _db: ConfigDb # 门面引用(表读取 + 诊断/致命通道——_init 注入)
var _diag: Callable # 诊断/致命通道(迭代 27 T6——门面类型反持解耦,validator 先例:签名 (level, category, message))
var _reset_warn: Callable # 去重注册表清空通道(reset_diagnostics 时点由门面驱动)
var _board_gen_rows_get: Callable # () -> Array——board_gen 行集活读(数据面经 Callable 注入,ConfigDb 类型环断根)
var _enemy_rows_get: Callable # () -> Array——enemy 行集活读
var _difficulty_get: Callable # () -> DifficultyTable(可空——表缺省合法态)
var _warn_dedup: Dictionary[String, bool] = {} # R1 四元组去重注册表(自 ConfigDb 迁入)
func _init(db_ref: ConfigDb) -> void:
_db = db_ref
func _init(diag_cb: Callable, reset_warn_cb: Callable, board_gen_rows_cb: Callable,
enemy_rows_cb: Callable, difficulty_cb: Callable) -> void:
_diag = diag_cb
_reset_warn = reset_warn_cb
_board_gen_rows_get = board_gen_rows_cb
_enemy_rows_get = enemy_rows_cb
_difficulty_get = difficulty_cb
# 去重注册表清空(门面 load_tables / reset_diagnostics 调用——与拆分前清空时点逐位一致)
@@ -27,21 +36,22 @@ func reset_warn_dedup() -> void:
# ---- R1 难度行解析(2026-09-18 用户拍板修订:ceil 向上取整保证偏难;域越界与内容缺口 Fatal 退出)----
func resolve_board_gen(L: int, D: int) -> Dictionary:
return _resolve_progress(_db.board_gen.rows, L, D, "board_gen")
return _resolve_progress(_board_gen_rows_get.call(), L, D, "board_gen")
func resolve_enemy_rowset(L: int, D: int) -> Array[Dictionary]:
# 行集数组形态(2026-10-07 敌人表行集多行批——原单行 Dictionary 退役):ceil 命中 (l*,d*) 后
# 返回该坐标全部行、按表文件行序(行序即契约——同坐标多行文件内相邻由 EnemyTable.validate 兜底)。
# 空集表达 = 空数组(生产表下 ceil 命中坐标恒有行,空集仅表空时可达——validate 已拦)。
return _resolve_progress(_db.enemy.rows, L, D, "enemy", true)
return _resolve_progress(_enemy_rows_get.call(), L, D, "enemy", true)
# 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数,越域值 clamp(GDD Edge Case——组合事故非配置事故)
func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary:
if _db.difficulty == null:
var dt = _difficulty_get.call()
if dt == null:
return {}
for row in _db.difficulty.rows:
for row in dt.rows:
if int(row.get("level_index", 0)) == L and int(row.get("node_depth", -1)) == D:
return {
"hp_mult": clampf(float(row.get("hp_mult", 1.0)), 0.1, 10.0),
@@ -52,7 +62,7 @@ func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary:
func _resolve_progress(rows: Array, L: int, D: int, table_id: String, return_all := false) -> Variant:
if L < 1 or D < 0:
_db._fatal("request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
_diag.call("fatal", "request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
return {}
var level_keys: Array[int] = []
for row in rows:
@@ -60,7 +70,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String, return_all
if not level_keys.has(lv):
level_keys.append(lv)
if level_keys.is_empty():
_db._fatal("structural", "%s 行集为空——锚行校验应已拦截" % table_id)
_diag.call("fatal", "structural", "%s 行集为空——锚行校验应已拦截" % table_id)
return {}
level_keys.sort()
var l_star := _ceil_key(level_keys, L)
@@ -96,7 +106,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String, return_all
if not _warn_dedup.has(dedup_key):
_warn_dedup[dedup_key] = true
var verb := "clamp 解析(超出最大行取最大值)" if missing.contains("clamp") else "ceil 解析(向上取整保证偏难)"
_db.push_diag("warning", "resolve_" + missing, "%s 请求 (%d,%d) %s → (%d,%d) [%s]——2026-09-18 拍板" % [table_id, L, D, verb, l_star, d_star, missing])
_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])
if return_all:
var all_out: Array[Dictionary] = []
for row in rows:
+1 -5
View File
@@ -95,14 +95,10 @@ func set_fx(fx: EffectEngine) -> void:
func _fx_hits(event: String, payload: Dictionary) -> Array:
if _fx == null:
return []
return _fx.dispatch(event, payload, Callable(self, "_fx_fact"))
return _fx.dispatch(event, payload, _fx_fact)
# 事件发射薄面(免施加观测位——enemy_hp_threshold 等现无订阅者的事件保留零扰动面)
func _fx_dispatch(event: String, payload: Dictionary) -> void:
_fx_hits(event, payload)
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数:charge_layers 编排器每轮注入。
# 迭代 20 T1——hp_ratio 补入〔背水 when 面,敌技 damage_taken 载荷不带血比例时回落〕;
# fx_reveal_request=揭示效果回调〔斩首学者 kill 订阅面——编排器注入,入队授予段末兑付〕)
+1 -18
View File
@@ -413,21 +413,4 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array,
## 按序改写族(收集→单点按声明序施加):modify_ticket 及一切改写窗口效果。
## 按序消耗族(按订阅序消耗共享资源,耗尽即止;可选 fallback):counter_spend。
## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、grant_ap(mode:transfer)、
## rewrite_board、apply_status——发射点逐条执行。
static func combo_of(kind: String) -> String:
match kind:
"add_ticket_rider", "attack_bonus", "grant_material", "counter_add", "shield", "heal", "ac_bonus", "damage_reduce", "bonus_delta":
return "additive"
"grant_ap":
return "additive" # mode:transfer 由发射点即时执行族承载(转移非加法)
"yield_mult", "damage_mult":
return "multiplicative"
"spawn_spell", "overflow", "spike":
return "any_true"
"modify_ticket":
return "ordered_write"
"counter_spend":
return "ordered_spend"
"open_ticket", "apply_status", "blast_scaled":
return "immediate"
return "immediate" # 默认即时执行(每条命中各自执行一次)
## rewrite_board、apply_status——发射点逐条执行。
-39
View File
@@ -1,39 +0,0 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-10-04(双层 BD 实施批 T1——标签档位激活引擎)
# 流派成型判据机制化(整合定稿 设计工具 1——以撒套装/小骨铭文先例):
# 同流派标签 relic 计数 ≥2 → 档一(流派核心规则);≥4 → 档二(完全体);<2 = 散件(单体保底——
# 每件效果独立生效,档位只做质变不做启动,反例 B-2)。
# 纯函数零 await 零 RNG 零副作用(Attunement 求值服务同款纪律);核心层零特判延伸——引擎只读
# feat_table 行集与持有集,接线点消费 tiers/hooks 投影。
class_name FeatsEngine
extends RefCounted
## 档位求值:feat_ids 持有集 × 行集 → {archetype: tier}(tier ∈ 0/1/2;无行流派不出现)
static func tiers_for(feat_ids: Array, table: FeatTable) -> Dictionary:
var counts := {}
for rid in feat_ids:
var row: Dictionary[Variant, Variant] = table.row_by_id(String(rid)) if table != null else {}
if row.is_empty():
continue
var arch := String(row.get("archetype", ""))
counts[arch] = int(counts.get(arch, 0)) + 1
var out := {}
for arch in counts.keys():
var n := int(counts[arch])
if n >= 4:
out[arch] = 2
elif n >= 2:
out[arch] = 1
else:
out[arch] = 0
return out
## 挂点投影随 T3 退役(内容数据驱动扩展批——hook 词族消亡:订阅投影=效果引擎 rebuild 装载序
## (feats 表序),投影稳定性由 EffectEngine._triggers 声明序承载)
## 成型判读(读数面口径):流派 tier ≥1 = 成型(档一即判——需求卡片完成定义「标签+档位激活引擎可判成型」)
static func is_formed(tiers: Dictionary, archetype: String) -> bool:
return int(tiers.get(archetype, 0)) >= 1
-1
View File
@@ -1 +0,0 @@
uid://6d12lkx5w2x8
+1 -1
View File
@@ -160,7 +160,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
# 三键 {atk, max_hp, armor_class}、缺键/-1 = 0、负值 clamp 0。游戏直入口仍恒白板——
# 注入面只服务数值验证工具,非玩家可达路径)
_external = false
_rest_handler = Callable(self, "rest_heal_amount")
_rest_handler = rest_heal_amount
_ledger = {"hero_hp": 0, "gp": 0}
var row := _find_level_row(level_index)
if row.is_empty():
-4
View File
@@ -38,10 +38,6 @@ func _init(db_ref: ConfigDb, action_economy_ref: ActionEconomy) -> void:
# ---- 板态 ----
func has_board() -> bool:
return _board != null
# 板产物只读出口(E2 收口——战斗结算 快照 / 组合根 rebuild / 探针经此取板,禁再直读字段)
func current_board() -> BoardData:
return _board
-8
View File
@@ -432,14 +432,6 @@ func set_attune_refresh_level(n: int) -> void:
# 同调位计数源组装(Attunement 服务取数口——clear_count = 逐关通关次数合计〔含重复关,底稿 §2.1 口径〕;
# clear_count(level_index) 逐关访问器零改动复用)
func attunement_counters() -> Dictionary:
var total := 0
var counts: Dictionary[Variant, Variant] = _data["meta"]["level_clear_counts"]
for k in counts.keys():
total += int(counts[k])
return {"clear_count": total, "boss_kill_count": boss_kill_count()}
func recent_summaries() -> Array:
return _data["meta"]["recent_summaries"].duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
-16
View File
@@ -65,14 +65,6 @@ func inventory_count() -> int:
# 持有数(迭代 10 奖励重构批——同调位上限判定读数:只计未消耗件;Used 不可逆即不再持有)
func holding_count() -> int:
var n := 0
for rec in _items:
if not bool(rec.get("used", false)):
n += 1
return n
# 同调位守卫谓词——迭代 11 第①批 T7 退役(读取源 transitional_sync_slots 过渡键已随表退役;
# 容量闸正主 = 营地装配面 Attunement.slots 满位禁选。函数体保留空壳防四散引用误删遗漏——
# T8 冒烟「过渡键读取点归零」断言覆盖)
@@ -80,14 +72,6 @@ func holding_count() -> int:
# 持有件记录(迭代 10 追加批——奖励节点替换子交互数据源:只列未消耗件,handle 定位丢弃目标)
# 丢弃(同调位替换交互节点渠道——腾位收新;与 Used 消费分轨的一次性移除,快照不再含)
func discard_item(handle: int) -> bool:
for i in _items.size():
if int(_items[i].get("handle", -1)) == handle and not bool(_items[i].get("used", false)):
_items.remove_at(i)
return true
return false
# 板级零信息可用性预判(图标置灰/入口禁用判据——GDD「整张图都没东西可看」级):
# 真视恒可用(零雷窗口=信息非空);AP 类恒可用(组合判据取 OR——任一效果有信息即可用);
# 纯预读行 = 板上还有未开格才可用。(积点件不入使用面——反哺批⑤自动兑付无玩家入口)