fix(review-batch3): 类型收紧生产清零137处(P1-2三步+延伸)+autoload class_name(ConfigDb/RngServiceCore)与_init标注(P2-1)+validate_skill_pool_links/ShopTable标注(P2-4)+相位enum外String面(P2-2)+typed-rows探针实证.tres装载转换;smoke缝合20处rows.assign+str(bb)同语义;审计生产双归零(240→103/83→59全测试链),单测7套绿,冒烟576/0同基线

This commit is contained in:
DKChan committed 2026-09-29 07:01:29 +08:00
1 parent f5edb65d25
commit 104cea6461
46 files changed
+233 -218

No files matched your search

+29 -16
View File
@@ -13,19 +13,23 @@ const PHASE_IDLE := "Idle"
const PHASE_ACTIVE := "Active"
const PHASE_WAITING := "Waiting"
var db
# 相位轴(P6 状态机——code-review 2026-09-29 P2-2):内部 enum + match,外部 phase getter 保持
# String 面(presentation_snapshot 与冒烟链的 "Idle"/"Active"/"Waiting" 对照契约零变化)。
enum Phase { IDLE, ACTIVE, WAITING }
var db: ConfigDb
# 相位与账本只读出口(E5 封装收口 2026-09-13):外部读取经 getter-only 属性(架构文档
# "ap/rap/phase 只读" 的机制化),写入只经 notify_* / request_* 方法通道。
var _phase: String = PHASE_IDLE
var _phase: int = Phase.IDLE
var phase: String:
get: return _phase
get: return _phase_text(_phase)
var _ap := 0
var ap: int:
get: return _ap
var _rap := 0
var rap: int:
get: return _rap
var _last_round_end: Dictionary = {} # {path_tag, continuation_hint}——排雷玩法 组包 round_end 的合法数据源
var _last_round_end: Dictionary[String, String] = {} # {path_tag, continuation_hint}——排雷玩法 组包 round_end 的合法数据源
var last_round_end: Dictionary:
get: return _last_round_end
var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start
@@ -33,10 +37,19 @@ var _next_round_mode := "" # 调试观测量(同板续轮 /
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
func _init(db_ref) -> void:
func _init(db_ref: ConfigDb) -> void:
db = db_ref
func _phase_text(p: int) -> String:
match p:
Phase.ACTIVE:
return PHASE_ACTIVE
Phase.WAITING:
return PHASE_WAITING
return PHASE_IDLE
func _cost(kind: String) -> int:
var ae = db.action_economy
match kind:
@@ -54,7 +67,7 @@ func _cost(kind: String) -> int:
# 通用扣减(排雷玩法 用于旗动作;挖握走 request_dig——cleared 仲裁需要 clears_map 参数)
func try_spend(action_kind: String) -> bool:
if phase != PHASE_ACTIVE:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
return _spend_and_arbitrate(action_kind, false)
@@ -62,7 +75,7 @@ func try_spend(action_kind: String) -> bool:
# 排雷玩法 挖掘专用:chain_size = 连锁附加格数(不含首格);clears_map = 本次动作后全图安全格开完
func request_dig(chain_size: int, clears_map: bool) -> bool:
if phase != PHASE_ACTIVE:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
return _spend_and_arbitrate("dig", clears_map)
@@ -86,7 +99,7 @@ func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool:
# 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)
func request_stop() -> bool:
if phase != PHASE_ACTIVE:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
_rap += _ap
@@ -100,7 +113,7 @@ func request_stop() -> bool:
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
func request_refresh() -> bool:
if phase != PHASE_ACTIVE:
if _phase != Phase.ACTIVE:
_reject("phase")
return false
if _rap < db.action_economy.rap_refresh_cost:
@@ -111,7 +124,7 @@ func request_refresh() -> bool:
# 战斗结算 回发:结算完成(仅记录编排意图;phase 停留 Waiting——AC-14 门不被绕过)
func notify_settlement_done(next_round_mode: String) -> bool:
if phase != PHASE_WAITING:
if _phase != Phase.WAITING:
_reject("phase")
return false
_settlement_consumed = true
@@ -130,25 +143,25 @@ func pending_ap_delta() -> int:
func notify_round_start() -> bool:
if phase == PHASE_IDLE:
if _phase == Phase.IDLE:
pass # 战斗首轮唯一出口
elif phase == PHASE_WAITING and _settlement_consumed:
elif _phase == Phase.WAITING and _settlement_consumed:
pass
else:
_reject("phase")
return false
_ap = maxi(0, db.action_economy.ap_per_round + _pending_ap_delta) # 每轮基线 + 排队修正(下限 0)
_pending_ap_delta = 0
_phase = PHASE_ACTIVE
_phase = Phase.ACTIVE
_settlement_consumed = false
return true
func notify_combat_end() -> bool:
if phase != PHASE_WAITING:
if _phase != Phase.WAITING:
_reject("phase")
return false
_phase = PHASE_IDLE
_phase = Phase.IDLE
_rap = 0 # RAP 战斗域清零(用户拍板;HUD框架可见反馈义务)
_ap = 0
return true
@@ -160,7 +173,7 @@ 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}
_phase = PHASE_WAITING
_phase = Phase.WAITING
func _reject(source: String) -> void:
+1 -1
View File
@@ -66,7 +66,7 @@ var _base_override := {"atk": 0, "max_hp": 0, "armor_class": 0}
var _first_script := false # 首次脚本模式(关卡节点图生成 v0.4——true = 全关零抽签取池内模板 0;测量夹具恒 false) # 永久强化 flat 注入(元进度实现批——冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板)
func _init(db_ref, rng_ref) -> void:
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
db = db_ref
rng = rng_ref
+15 -15
View File
@@ -38,8 +38,8 @@ const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward"]
var db
var rng
var db: ConfigDb
var rng: RngServiceCore
# E1 封装收口(2026-09-13):协作对象 board_generator/enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/
# diagnostic_snapshot() 成文出口(呈现层链式穿透 settlement._enemy_system.hero_unit() 的根因收口)。
# minesweeper/action_economy 保持公有 = 架构文档 API Boundaries 的 HUD框架消费面(意图提交 settle_batch + compute_move_path
@@ -52,13 +52,13 @@ var _status_effects: StatusContainer
var _reward_economy: RewardEconomy
var _damage: DamageController # 伤害控制器(伤害管线单据化批 2026-09-24——单据唯一兑付管线)
var encounter: Dictionary = {}
var encounter: Dictionary[String, Variant] = {}
var round_index := 0
var battle_result := "" # "" / "victory" / "defeat" / "fatal_error"
var escrow := 0 # 战斗内 GP 缓冲(战斗域中间态——begin_battle 清零;gp_gain/gp_loss 兑付累记;随 gp_dropped 上抛即弃,不入钱包)
var _facts_reveals: Array = []
var _facts_flags: Array = []
var _facts_reveals: Array[Dictionary] = []
var _facts_flags: Array[Dictionary] = []
var _facts_path := ""
var _settling := false
var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_cap_per_round)
@@ -71,7 +71,7 @@ var _perfect_hit_false := 0
var _hero_atk := 0 # 主角攻击基值快照(encounter.hero_atk;E4 收口——表值运行期不可变)
func _init(db_ref, rng_ref) -> void:
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
db = db_ref
rng = rng_ref
_board_generator = BoardGenerator.new()
@@ -94,7 +94,7 @@ func begin_battle(enc: Dictionary) -> bool:
if round_index != 0:
push_warning("[战斗结算] begin_battle 重复受理")
return false
encounter = enc
encounter.assign(enc)
escrow = 0 # 战斗域零 GP 持久(GP+商店房实施批——每场战斗缓冲从零起算)
_damage.set_hero_ac_override(int(enc.get("hero_armor_class", -1))) # 主角护甲合流入参(伤害控制器 defender_ac 消费——口径同原 _defender_ac)
db.set_reload_gate(true)
@@ -150,7 +150,7 @@ func _install_new_board(during_round := false) -> bool:
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var eligible: Array = []
var eligible: Array[Dictionary] = []
for c in board.safe_cells():
if not r0.has(c):
eligible.append({"cell": c})
@@ -189,7 +189,7 @@ func _place_variants(board: BoardData) -> void:
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var candidates: Array = []
var candidates: Array[Vector2i] = []
for c in board.safe_cells():
if not r0.has(c):
candidates.append(c)
@@ -203,7 +203,7 @@ func _place_variants(board: BoardData) -> 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 = []
var free: Array[Vector2i] = []
for c in candidates:
# 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量
# (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言)
@@ -217,7 +217,7 @@ func _place_variants(board: BoardData) -> void:
var pack: Array = [seed_c]
var pack_set := {seed_c: true}
while pack.size() < pack_size:
var fringe: Array = []
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) \
@@ -628,10 +628,10 @@ func _emit_composition_effects(side: String, ri: int) -> void:
# ---- 结算快照(playtest 数值分析数据面——每次结算全量状态;运行时零决策读取)----
func _emit_settlement_snapshot(verdict: String) -> void:
var enemies: Array = []
var enemies: Array[Dictionary] = []
for u in _enemy_system.query_alive("enemy"):
enemies.append({"id": String(u.enemy_id), "hp": int(u.hp), "max_hp": int(u.max_hp), "atk": int(u.atk), "pending_skill": _enemy_system.pending_action(u), "armor": String(u.get("armor_type", "light")), "armor_class": int(u.get("armor_class", 0))})
var buffs: Array = []
var buffs: Array[Dictionary] = []
for eff in _status_effects.query_active("hero"):
buffs.append({"id": eff.effect_id, "side": "hero", "layers": eff.layers, "left": eff.remaining_settlements})
for eff in _status_effects.query_active("enemy"):
@@ -746,10 +746,10 @@ func _roll_gp_drop() -> int:
# (敌人技能池批批 2 定稿:beat/ring_size/ring_index 拆除 → pending_* 三字段 + 敌方增益 effects 投影;池与权重不公开;
# 八型批 2026-09-20:ambush_layers 拆除——伏击退役,警觉并入 effects 投影)hero_effects[](增益状态 query_active 投影)
func presentation_snapshot() -> Dictionary:
var enemies: Array = []
var enemies: Array[Dictionary] = []
var all: Array = _enemy_system.all_enemies()
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行,八型批 2026-09-20)
var enemy_effects: Array = []
var enemy_effects: Array[Dictionary] = []
for eff in _status_effects.query_active("enemy"):
var erow: Dictionary = db.status_effect_row(eff.effect_id)
if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]:
+4 -4
View File
@@ -49,7 +49,7 @@ func has_variant(c: Vector2i, vid: String) -> bool:
func neighbors8(c: Vector2i) -> Array:
var out: Array = []
var out: Array[Vector2i] = []
for dy in range(-1, 2):
for dx in range(-1, 2):
if dx == 0 and dy == 0:
@@ -61,7 +61,7 @@ func neighbors8(c: Vector2i) -> Array:
func spawn_cells() -> Array:
var out: Array = []
var out: Array[Vector2i] = []
for dy in range(-1, 2):
for dx in range(-1, 2):
var c := Vector2i(spawn_center.x + dx, spawn_center.y + dy)
@@ -71,7 +71,7 @@ func spawn_cells() -> Array:
func all_cells() -> Array:
var out: Array = []
var out: Array[Vector2i] = []
for y in height:
for x in width:
out.append(Vector2i(x, y))
@@ -79,7 +79,7 @@ func all_cells() -> Array:
func safe_cells() -> Array:
var out: Array = []
var out: Array[Vector2i] = []
for c in all_cells():
if is_safe(c):
out.append(c)
+3 -3
View File
@@ -83,7 +83,7 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar
var spawn := {}
for c in b.spawn_cells():
spawn[c] = true
var domain: Array = []
var domain: Array[Vector2i] = []
for c in b.all_cells():
if not spawn.has(c):
domain.append(c)
@@ -198,7 +198,7 @@ func measure_inferability(b: BoardData) -> Dictionary:
func _build_constraints(b: BoardData, r: Dictionary, known_mines: Dictionary, safe_set: Dictionary) -> Array:
var out: Array = []
var out: Array[Dictionary] = []
for c in r.keys():
if not safe_set.has(c):
continue # 只对已开安全格的数字建约束
@@ -273,7 +273,7 @@ func legality(b: BoardData, params: Dictionary, db) -> bool:
func _legality_faults(b: BoardData, params: Dictionary) -> Array:
var faults: Array = []
var faults: Array[String] = []
# ① 出生区无雷 ② 出生区∪R₀ 无变异 ③ 数字恒真 ④ R₀ ≠ S(排雷玩法 修订义务)
for c in b.spawn_cells():
if b.is_mine(c):
+12 -11
View File
@@ -4,6 +4,7 @@
# ConfigDB autoload(数据配置)——数值表单点访问入口(Rule 2)+ 校验双通道(Rule 6)+ R1 行解析。
# 实现 GDD:design/archive/gdd-2026-09-14/d2-data-config.md(系统 ID D2·数据配置——registry 追溯锚,命名规范见 CLAUDE.md)
# 切片注:headless --script 模式下由冒烟脚本显式实例化(数据配置 AC 前言);玩法代码禁止绕过本单点自建 load()。
class_name ConfigDb
extends Node
const SCHEMA_VERSION := 1
@@ -36,16 +37,16 @@ var reward: RewardTable
var settlement: SettlementTable
var level: LevelTable
var difficulty: DifficultyTable
# GP+商店房实施批(2026-09-21):ShopTable 暂持无类型注记——schema class_name 落籍归并行数据面,
# 落籍后可收紧为 `var shop: ShopTable`(消费面一律走下方 shop_* 访问器,禁直读行集)
var shop = null
# GP+商店房实施批(2026-09-21):ShopTable 类型化持用(schema class_name 已落籍——code-review 2026-09-29 P2-4 收口;
# 消费面一律走下方 shop_* 访问器,禁直读行集)
var shop: ShopTable = null
# 元进度实现批(2026-09-21):MetaTable 类型化持用(schema 与表同批落籍,无 shop 先例的落籍间隙问题)
var meta: MetaTable
var adventure: AdventureTable
var is_ready := false
var _diagnostics: Array = [] # {severity, category, message}
var _warn_dedup: Dictionary = {} # R1 四元组去重注册表
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
var _warn_dedup: Dictionary[String, bool] = {} # R1 四元组去重注册表
var _reload_gate := false # 数据配置 EC8——战斗结算 唯一调用方
@@ -170,7 +171,7 @@ func _cross_validate(loaded: Dictionary) -> void:
for lrow in lv.rows:
var li: int = int(lrow.level_index)
# 模板池感知(关卡节点图生成 v0.4——池内全模板跨表校验,不只 row.nodes)
var node_lists: Array = []
var node_lists: Array[Array] = []
var tpls: Array = lrow.get("templates", [])
if tpls.is_empty():
node_lists.append(lrow.get("nodes", []))
@@ -199,8 +200,8 @@ func _cross_validate(loaded: Dictionary) -> void:
# proposal-status-vocabulary §1.2)+ 施放类 side 守卫(增益引用 enemy 侧 / 妨害引用 hero 侧状态行)。
# status_table 显式入参:装载序上本校验先于成员赋值执行(self.status_effects 尚为 Nil)——装载通道传 loaded 表,
# 冒烟直验传 _db.status_effects;传 null 时 side 守卫跳过(其他校验不受影响)。
func validate_skill_pool_links(skill_table, enemy_table, status_table = null) -> Array:
var errs: Array = []
func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable, status_table: StatusEffectTable = null) -> Array:
var errs: Array[Dictionary] = []
var skill_types := {}
for row in skill_table.rows:
skill_types[String(row.get("skill_id", ""))] = String(row.get("type", ""))
@@ -267,7 +268,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Diction
if L < 1 or D < 0:
_fatal("request_oob", "%s 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [table_id, L, D])
return {}
var level_keys: Array = []
var level_keys: Array[int] = []
for row in rows:
var lv := int(row.get("level_index", 0))
if not level_keys.has(lv):
@@ -280,7 +281,7 @@ func _resolve_progress(rows: Array, L: int, D: int, table_id: String) -> Diction
var l_clamped := l_star < 0
if l_clamped:
l_star = int(level_keys[-1]) # 超出最大行 → 取最大行封顶(2026-09-18 追加拍板:可取范围内向上取整,超出用最大值)
var depth_keys: Array = []
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))
@@ -328,7 +329,7 @@ func _ceil_key(sorted_keys: Array, request: int) -> int:
# board_type 词表——biome_tags 系旧观念残留退役)----
func variant_pool(level_index: int, board_type: String) -> Array:
var pool: Array = []
var pool: Array[Dictionary] = []
for row in variation.rows:
if int(row.get("unlock_level", 999)) > level_index:
continue
+2 -2
View File
@@ -48,14 +48,14 @@ const TARGET_FRONT_ALIVE := "front_alive_enemy"
const TARGET_HERO := "hero"
const TARGET_ALL_ALIVE := "all_alive_enemies"
var db
var db: ConfigDb
var rng
var units # UnitContainer——目标解析/伤害交付/存活查询(句柄即 Dictionary 引用)
var effects # StatusContainer——池值/AC 型效果读取(增益状态 只读消费面)
var emit_row_cb: Callable # 行协议注入口:func(type, source_class, origin_cell, ri, display, value, extra, highlight)
var policy := POLICY_SEGMENT # 结账时机开关(2026-09-24 用户拍板第二步拨 B=攒批段界统一结;回切 A 改回 POLICY_IMMEDIATE 即还原迁移前行为)
var _queue: Array = [] # 未结单据(B 模式攒批;A 模式恒空)
var _queue: Array[Dictionary] = [] # 未结单据(B 模式攒批;A 模式恒空)
var _outcomes := {} # ticket_id → "redeemed"/"zero"/"voided"(结账后可查——清图高光份数消费)
var _seq := 0 # 流水号
var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废)
+13 -13
View File
@@ -40,9 +40,9 @@ signal board_changed()
signal level_finished(payload: Dictionary) # {result, hp, node_index, node_id, node_type}
signal wallet_changed(event: String, gp: int, extra: Dictionary) # GP 钱包事件投影:gain{amount}/buy{product,price}/clear——试玩日志与商店覆盖层刷新源(GP+商店房实施批)
var db
var rng
var level_row: Dictionary = {}
var db: ConfigDb
var rng: RngServiceCore
var level_row: Dictionary[String, Variant] = {}
var node_index := -1
# ---- 账本载体(冒险结构实现批——漂移②清偿)----
# 冒险模式 = AdventureRunner 共享字典(Dictionary 引用共享——hero_hp/gp 属性读写直接落冒险账本);
@@ -68,12 +68,12 @@ var hero_hp_pct := 0
var _running := false
var level_result := "" # "" / "victory" / "defeat"
var _awaiting_choice := false # 选路态(兑付确认后、pick 前为零自动推进的可见停点)
var _candidates: Array = [] # 当前选路候选(节点引用数组)
var visited: Array = [] # 已兑付/到达节点序列(呈现层图条数据源;含当前节点)
var _candidates: Array[Dictionary] = [] # 当前选路候选(节点引用数组)
var visited: Array[Dictionary] = [] # 已兑付/到达节点序列(呈现层图条数据源;含当前节点)
var _graph_mode := false # 图形态缓存(行集任一节点带 next;单链 = false)
func _init(db_ref, rng_ref) -> void:
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
db = db_ref
rng = rng_ref
@@ -142,7 +142,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
break
hero_hp = effective_max_hp()
rng.new_adventure(seed_val) # 关卡起点 = 冒险起点(切片口径;跨关接续归 冒险结构)
level_row = _resolve_template_row(row, template_pick) # 池解析在播种后(node_map 抽签须已播种——EC7)
level_row.assign(_resolve_template_row(row, template_pick)) # 池解析在播种后(node_map 抽签须已播种——EC7)
_running = true
var entry := _entry_node()
if entry.is_empty():
@@ -177,7 +177,7 @@ func start_level_external(level_index: int, ledger: Dictionary, bonus_ref: Dicti
base_override = base_override_ref
_shop_purchased = purchased_ref
_rest_handler = rest_handler
level_row = _resolve_template_row(row, template_pick)
level_row.assign(_resolve_template_row(row, template_pick))
node_index = -1
level_result = ""
_awaiting_choice = false
@@ -286,11 +286,11 @@ func _out_ids(node: Dictionary) -> Array:
# 出边候选(节点引用数组——选路口/呈现层共用读面)
func _out_nodes(node: Dictionary) -> Array:
func _out_nodes(node: Dictionary) -> Array[Dictionary]:
var ids := {}
for n in nodes():
ids[_node_id(n)] = n
var out: Array = []
var out: Array[Dictionary] = []
for tid in _out_ids(node):
if ids.has(tid):
out.append(ids[tid])
@@ -304,7 +304,7 @@ func next_candidates() -> Array:
# 当前选路候选编号表(pick_next_node 的合法入参域——编号空间解析单一出口)
func next_candidate_ids() -> Array:
var out: Array = []
var out: Array[int] = []
for c in _candidates:
out.append(_node_id(c))
return out
@@ -326,7 +326,7 @@ func node_out_ids(node: Dictionary) -> Array:
# 已到节点编号序列(只读投影——编号空间解析单一出口)
func visited_ids() -> Array:
var out: Array = []
var out: Array[int] = []
for v in visited:
out.append(_node_id(v))
return out
@@ -493,7 +493,7 @@ func close_wallet() -> void:
# 商店覆盖层数据源(只读投影——现价/已购档数在此现算,呈现层禁复算定价公式)
func shop_products() -> Array:
var out: Array = []
var out: Array[Dictionary] = []
for prow in db.shop_products():
out.append({
"product_id": String(prow.get("product_id", "")),
+6 -6
View File
@@ -16,11 +16,11 @@
class_name MetaProgression
extends RefCounted
var db
var save
var db: ConfigDb
var save: SaveManager
func _init(db_ref, save_ref) -> void:
func _init(db_ref: ConfigDb, save_ref: SaveManager) -> void:
db = db_ref
save = save_ref
@@ -83,7 +83,7 @@ func _workshop_progress(channel: String) -> int:
# 工坊六行只读投影(呈现层数据源):{item_id, display_name, threshold, unlocked, progress}
func workshop_status() -> Array:
var out: Array = []
var out: Array[Dictionary] = []
for row in db.meta_workshop_items():
var iid := String(row.get("item_id", ""))
out.append({
@@ -98,7 +98,7 @@ func workshop_status() -> Array:
# 达标未解锁 → unlock_item + 写盘;返回新解锁 item_id 列(冒险收束后与营地打开时各调一次)
func refresh_workshop_unlocks() -> Array:
var new_unlocks: Array = []
var new_unlocks: Array[String] = []
for row in db.meta_workshop_items():
var iid := String(row.get("item_id", ""))
if save.item_unlocked(iid):
@@ -118,7 +118,7 @@ func refresh_workshop_unlocks() -> Array:
func settle_adventure(summary: Dictionary) -> Dictionary:
# 首次冒险布尔位置位(关卡节点图生成 v0.4——完成/失败/放弃三结局同经本收束挂点,写入只在收束)
save.mark_first_adventure_done()
var drops: Array = []
var drops: Array[Dictionary] = []
for li in range(1, int(summary.get("levels_cleared", 0)) + 1):
var first: bool = not save.boss_first_kill_marked(li)
var row: Dictionary = db.meta_boss_keystone_row(li)
+8 -8
View File
@@ -15,18 +15,18 @@ signal round_end_board(payload: Dictionary) # 双发射源之棋盘路径(
const SUB_REASONS := ["not_adjacent", "target_revealed", "target_concealed", "flag_present", "no_path", "out_of_catalog"] # flag_absent 随 flag_pull 退役(2026-09-19)
var db
var action_economy
var db: ConfigDb
var action_economy: ActionEconomy
# 棋盘状态私有(E2 封装收口 2026-09-13):对外只暴露 current_board()/reveal_record() 等查询面,
# 禁止跨模块直读/直写原始字典——先例教训 = board_view 绕过查询面直读 revealed。
var _board: BoardData = null
var _revealed := {} # Vector2i → {"kind": "safe"/"mine", "digit": int}
var _flags := {} # Vector2i → true(棋盘单态;跨轮保留随图存续)
var _hero := Vector2i.ZERO
var _spawn: Dictionary = {}
var _spawn: Dictionary[Vector2i, bool] = {}
func _init(db_ref, action_economy_ref) -> void:
func _init(db_ref: ConfigDb, action_economy_ref: ActionEconomy) -> void:
db = db_ref
action_economy = action_economy_ref
@@ -125,7 +125,7 @@ func retire_board(during_round := false) -> Dictionary:
# 四不变式(诊断类别封闭枚举):inv1 出生区雷 / inv2a 出生区变异 / inv2b R₀ 变异 / inv3 数字恒真 / inv4 R₀=S
func _install_invariant_faults(b: BoardData) -> Array:
var faults: Array = []
var faults: Array[String] = []
var r0 := {}
for c in b.compute_r0():
r0[c] = true
@@ -171,7 +171,7 @@ func _initial_expansion_order(b: BoardData) -> Array:
# ---- 批次结算(唯一提交入口;单意图批 = 平凡形 ≡ 便捷 API)----
func settle_batch(intents: Array) -> Array:
var outcomes: Array = []
var outcomes: Array[Dictionary] = []
if intents.is_empty():
return outcomes
if action_economy.phase != action_economy.PHASE_ACTIVE or _board == null:
@@ -179,7 +179,7 @@ func settle_batch(intents: Array) -> Array:
outcomes.append(_outcome(false, "rejected", "phase", ""))
return outcomes
# F4 语义序仲裁:C_dig(真模拟清图)→ C_stop → C_other;类内稳定序 = 到达序
var indexed: Array = []
var indexed: Array[Dictionary] = []
for i in intents.size():
indexed.append({"i": i, "intent": intents[i], "cls": _classify(intents[i])})
indexed.sort_custom(func(a, b):
@@ -395,7 +395,7 @@ func compute_move_path(target: Vector2i) -> Array:
queue.append(n)
if not prev.has(target):
return []
var path: Array = []
var path: Array[Vector2i] = []
var cur := target
while cur != _hero:
path.push_front(cur)
+8 -8
View File
@@ -12,10 +12,10 @@
class_name RewardEconomy
extends RefCounted
var db
var rng
var db: ConfigDb
var rng: RngServiceCore
var _phase := "unrolled" # unrolled → ready → retired
var _eligible: Array = [] # [{cell}] 行主序规范序
var _eligible: Array[Dictionary] = [] # [{cell}] 行主序规范序
var _mapping := {} # Vector2i → reward_id(正向行身份——格面资源类型由 cell_type 承载)
var _power_roll := {} # Vector2i → 区间行 roll 值(身份批内追加取号)
var _clear_power := -1 # 清图槽区间行 roll 值(-1 = 定值行)
@@ -25,7 +25,7 @@ var _clear_granted := false
var _clear_snapshot := {}
func _init(db_ref, rng_ref) -> void:
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
db = db_ref
rng = rng_ref
@@ -43,7 +43,7 @@ func roll_board(eligible: Array) -> bool:
return false
# 行主序规范化 + 去重(排序责任不外推给 战斗结算)
var seen := {}
var list: Array = []
var list: Array[Dictionary] = []
for e in eligible:
var c: Vector2i = e.cell
if seen.has(c):
@@ -175,7 +175,7 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary:
# ---- F6 当量供给(行动经济 条款①对照实物输入——全活行投影 + tier_min_buff)----
func equivalence_supply() -> Dictionary:
var live: Array = []
var live: Array[Dictionary] = []
var tier_min_buff := 99
for row in db.reward.rows:
if int(row.weight) <= 0:
@@ -206,7 +206,7 @@ func weighted_pick(rows: Array, r: int) -> String:
func _rollable_rows() -> Array:
# 摇号池 = cell 池正向行 weight>0(负向行纯字典不消费权重;封锁格不在本函数职责域——清单由调用方裁剪)
var out: Array = []
var out: Array[Dictionary] = []
for row in db.reward.rows:
if String(row.get("pool_tag", "")) == "cell" and String(row.get("polarity", "")) == "positive" and int(row.weight) > 0:
out.append(row)
@@ -214,7 +214,7 @@ func _rollable_rows() -> Array:
func _pool_rows(pool_tag: String) -> Array:
var out: Array = []
var out: Array[Dictionary] = []
for row in db.reward.rows:
if row.pool_tag == pool_tag and int(row.weight) > 0:
out.append(row)
+1
View File
@@ -11,6 +11,7 @@
# splitmix64 finalizer 变体终末混淆,见 _mix64 注记;并流前分盐黄金向量随批作废,新向量按单盐重登记(scope 0-3 与
# 旧 board_gen 向量逐位连续——单盐沿用原 board_gen 盐)。
# 取号语义:全新冒险首个 open_stream = 全局 scope 0;事件日志逐次落盘 user://logs/rng_log_<master_seed>.jsonl。
class_name RngServiceCore
extends Node
# 用途标签封闭词表(并流前七流名全保留;新增用途走扩签通道 = 改代码 + 评审——清单封闭语义由「流」改「标签」)
+4 -4
View File
@@ -33,8 +33,8 @@ const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合
var _path: String # 存档文件路径(默认 user://save.json;冒烟注入隔离路径)
var _read_only := false # 高版本拒读置位——write_to_disk 一律拒绝(契约 4 禁写)
var _data: Dictionary = {} # 内存数据模型(_init 即内置默认;load 后为档案内容)
var _migrations: Dictionary = {} # from_version → Callable(data)->data(升一版;生产链现为空——骨架先行)
var _data: Dictionary[String, Variant] = {} # 内存数据模型(_init 即内置默认;load 后为档案内容)
var _migrations: Dictionary[int, Callable] = {} # from_version → Callable(data)->data(升一版;生产链现为空——骨架先行)
func _init(path: String = "user://save.json") -> void:
@@ -44,7 +44,7 @@ func _init(path: String = "user://save.json") -> void:
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
func _default_data() -> Dictionary:
func _default_data() -> Dictionary[String, Variant]:
return {
"version": SAVE_FORMAT_VERSION,
"meta": {
@@ -222,7 +222,7 @@ func _corrupt_fallback(reason: String) -> bool:
# 迁移后/低版本档字段补齐(改版不弃档:缺新域按默认补齐,既有值保留)
func _normalize(data: Dictionary) -> Dictionary:
func _normalize(data: Dictionary) -> Dictionary[String, Variant]:
var merged := _default_data()
for key in data:
merged[key] = data[key]
+5 -5
View File
@@ -8,12 +8,12 @@
class_name StatusContainer
extends RefCounted
var db
var db: ConfigDb
var _open := false
var _instances: Array = [] # 插入序;元素 = {effect_id, layers, remaining, source_class, origin_cell, round_index, snap_m, snap_unit, snap_display}
var _instances: Array[Dictionary] = [] # 插入序;元素 = {effect_id, layers, remaining, source_class, origin_cell, round_index, snap_m, snap_unit, snap_display}
func _init(db_ref) -> void:
func _init(db_ref: ConfigDb) -> void:
db = db_ref
@@ -84,7 +84,7 @@ func _new_instance(effect_id: String, layers: int, dur: int, source: Dictionary,
# 只读投影(插入序稳定枚举;六归因字段 + display_name + 授予时快照)
func query_active(side: String) -> Array:
var out: Array = []
var out: Array[Dictionary] = []
for inst in _instances:
var row: Dictionary = db.status_effect_row(inst.effect_id)
if row.is_empty():
@@ -119,7 +119,7 @@ func decay_round() -> int:
if not _open:
return 0 # EC13 域闭兜底读法——容器恒空,无独立分支不诊断
var k := 0
var survivors: Array = []
var survivors: Array[Dictionary] = []
for inst in _instances:
inst.remaining -= 1
if inst.remaining <= 0:
+8 -8
View File
@@ -20,18 +20,18 @@ extends RefCounted
# 消费方 = 伤害控制器(怪死 → 其未结单据作废〔死亡即不动手〕;主角死 → 兑付终止旗)。
signal unit_died(u: Dictionary)
var db
var db: ConfigDb
# 单位容器私有(E3 封装收口 2026-09-13):hero/enemies 原始数组不再外露——读取经 hero_unit()/
# all_enemies() 投影面(死亡残位查询包含在内),写入只经 apply_damage/heal/set_pending_action/commit_action/
# tick_cooldowns/spawn/despawn。
# 句柄语义不变:投影返回的 Dictionary 引用即单位句柄(同 query_alive 先例)。
var _hero: Dictionary = {} # {hp, max_hp, atk, is_hero: true}
var _enemies: Array = [] # 行主序生成序;元素 = 单位 Dictionary
var _hero: Dictionary[String, Variant] = {} # {hp, max_hp, atk, is_hero: true}
var _enemies: Array[Dictionary] = [] # 行主序生成序;元素 = 单位 Dictionary
var _spawned := false
var _despawned := false
func _init(db_ref) -> void:
func _init(db_ref: ConfigDb) -> void:
db = db_ref
@@ -69,7 +69,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
for i in range(count):
var eff_row_atk: int = enemy_atk_override if enemy_atk_override >= 0 else int(row.atk)
# 实例池快照(proposal §2.2:开战把模板快照一份,冷却全零——与"生成拿到当时的表值快照"同一条快照纪律)
var pool_snap: Array = []
var pool_snap: Array[Dictionary] = []
for entry in row.get("pool", []):
pool_snap.append({
"skill_id": String(entry.get("skill_id", "")),
@@ -103,7 +103,7 @@ func query_alive(side: String) -> Array:
return []
if side == "hero":
return [] if (_hero.is_empty() or not _hero.alive) else [_hero]
var out: Array = []
var out: Array[Dictionary] = []
for u in _enemies:
if u.alive:
out.append(u)
@@ -118,7 +118,7 @@ func rollable_skills(u: Dictionary) -> Array:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("rollable_skills 失效句柄")
return []
var out: Array = []
var out: Array[Dictionary] = []
for e in u.pool:
if int(e.cooldown_left) <= 0:
out.append({"skill_id": String(e.skill_id), "weight": int(e.weight)})
@@ -130,7 +130,7 @@ func special_skills(u: Dictionary) -> Array:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("special_skills 失效句柄")
return []
var out: Array = []
var out: Array[Dictionary] = []
for e in u.pool:
var srow: Dictionary = db.skill_row(String(e.skill_id))
if not srow.is_empty() and String(srow.type) != "basic":