merge(master)+禁令残留清扫:并回 master 新进提交(gdstyle 闸件批/S9 卡/iter36 收尾),冲突 test-conventions §8.4 保重编号版+吸收 master 新条款「判据绑可观测提交面」(措辞去变异化);随批清扫——待办清单 gdstyle/S9 两条目「变异验证义务」改撤销证明口径;S9 活卡全量改写(契约 3/T2/D2/D4/来源行/对照表列头/审计面/两轮 grilling 记录——旧「闸件豁免」定界标注经用户全项目禁令推翻,验证义务统一撤销证明,禁令条款原文保留)
This commit is contained in:
commit
e89f486aa9
94 files changed
+4608
-3451
No files matched your search
@@ -42,7 +42,7 @@ var _pending_next_level := false # 大奖励已兑付、等 continue_to
|
||||
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
|
||||
var _override := [0, 0] # 调试浮窗百分比通道 [atk_pct, hp_pct](每关新 runner 都要应用)
|
||||
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;全零 = 白板)
|
||||
# 永久强化 flat 注入(冒险开始参数面 params.hero_override 存入,共享给 LevelRunner;全零 = 白板)
|
||||
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
|
||||
var _treasure_unlocked := {} # aux 解锁门控集(params.treasure_unlocked 接收面)
|
||||
var _attunement: Array[Variant] = [] # 同调装配集(params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||
@@ -99,7 +99,6 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_base_override = _normalize_override(params.get("hero_override", {}))
|
||||
hero_attack_type = String(params.get("attack_type", "")) # 两根类型轴快照(契约 8 锁死:开局后不随存档选中变化)
|
||||
hero_armor_type = String(params.get("armor_type", ""))
|
||||
_first_script = bool(params.get("first_script", 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
|
||||
@@ -144,6 +143,11 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
if bp.is_empty():
|
||||
push_error("[冒险结构] resume_adventure 拒绝——空断点快照")
|
||||
return false
|
||||
if int(bp.get("graph_gen_v2", 0)) != 1:
|
||||
# 旧代差断点(模板池时代的 node_id 与生成图句柄空间可碰撞错位)——显式拒绝,
|
||||
# 禁按新语义静默回放;旧模板图已随模板池退役,无迁移动作可做(凭空捏图=兜底图红线)。
|
||||
push_error("[冒险结构] resume_adventure 拒绝——断点缺生成图代差标记(graph_gen_v2),属换代前旧断点")
|
||||
return false
|
||||
master_seed = int(bp.get("master_seed", 0))
|
||||
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
|
||||
ledger = {"hero_hp": int(bp.get("hero_hp", 0)), "gp": int(bp.get("gp", 0))}
|
||||
@@ -191,7 +195,6 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
# 钳制用 flat 已含的保守口径即可,加算区恢复后其余战斗照常)。
|
||||
ledger["hero_hp"] = mini(int(ledger.get("hero_hp", 0)), maxi(1,
|
||||
int(round(float(int(db.hero.max_hp) + int(_base_override.get("max_hp", 0))) * (100.0 + float(_override[1])) / 100.0))))
|
||||
_first_script = false # 续玩模板已随快照强制(已抽模板零重抽)——脚本模式不重放
|
||||
_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
|
||||
@@ -844,14 +847,12 @@ func _start_current_level() -> bool:
|
||||
_runner.wallet_changed.connect(_forward_wallet_changed)
|
||||
_runner.level_finished.connect(_on_level_finished)
|
||||
_runner.set_hero_override(int(_override[0]), int(_override[1]))
|
||||
# 断点续玩分支:模板随快照强制(已抽模板零重抽零 RNG)、resume_state 带
|
||||
# 停点节点与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
||||
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
|
||||
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
||||
var resume_state: Dictionary[Variant, Variant] = {}
|
||||
var template_pick: int = 0 if _first_script else -1 # 首次脚本 = 每关固定模板 0 零抽签;否则逐关抽签
|
||||
if not _resume_bp.is_empty():
|
||||
resume_state = {"node_id": int(_resume_bp.get("node_id", -1)),
|
||||
"visited_ids": _resume_bp.get("visited_ids", []) as Array}
|
||||
template_pick = int(_resume_bp.get("template_pick", -1))
|
||||
# 接口①开始关卡:生命经共享账本交接(账本当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
||||
# _base_override 共享引用随载荷传入(永久强化 flat——LevelRunner.effective_* 叠加序第二项)。
|
||||
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
||||
@@ -860,7 +861,7 @@ func _start_current_level() -> bool:
|
||||
_runner.set_run_ledger(run_ledger) # 共享四账单类下传(run_ledger=四账类,ledger=关卡账)
|
||||
_runner.sync_attune_inventory() # 能力件骑库存面注入(随账下传同拍)
|
||||
var ok: bool = _runner.start_level_external(level_index, ledger, purchased,
|
||||
apply_rest, _base_override, true, template_pick, resume_state)
|
||||
apply_rest, _base_override, true, resume_state)
|
||||
if ok:
|
||||
_runner.set_attunement(_attunement) # 同调装配下传(enc 携带进战斗结算)
|
||||
_runner.set_feats(_feats) # 特性持有集下传(begin_battle enc.feats 求值面)
|
||||
@@ -1010,7 +1011,7 @@ func _snapshot_breakpoint() -> void:
|
||||
"shop_shelf": _runner.shop_bp_shelf() if _runner != null else [], # 货架入断点(离开店/非店停点 = 空架合法态)
|
||||
"shop_visit_buys": _runner.shop_bp_visit_buys() if _runner != null else 0, # 本次进店已购次数(A 链定价输入)
|
||||
"attunement_charges": run_ledger.attune_charges.duplicate(true), # 同调主动件充能余量随断点(save v4 域)
|
||||
"template_pick": _runner.active_template_index if _runner != null else -1,
|
||||
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
|
||||
"master_seed": master_seed,
|
||||
"rng_counter": rng.counter(),
|
||||
"keystone": keystone,
|
||||
|
||||
@@ -794,7 +794,7 @@ func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cel
|
||||
if halve_pct > 0 and polarity == "negative" and source_class == "mine_hit":
|
||||
row = row.duplicate()
|
||||
var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0))
|
||||
_emit_row("effect", source_class, cell, ri, "惊巢反制——挖雷惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||
_emit_row("effect", source_class, cell, ri, "惊巢反制——危险格挖掘惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||
row.power = half
|
||||
return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
|
||||
|
||||
@@ -51,60 +51,8 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
for s in required_sources:
|
||||
if not st_tab.source_label_map.has(s):
|
||||
push_diag.call("error", "segorder_violation", "source_label_map 缺必需键: %s" % s)
|
||||
# ④ LevelTable 节点悬空引用(battle/boss 节点:board_gen 侧须有精确行;enemy 侧按
|
||||
# (关卡序号, 行类) 带寻址——落带即设计语义,节点深度高于顶带键=顶 clamp 消解属调参事故,加载期直接拦。
|
||||
# 同关同一张图多终点 Boss 节点解析带键须互异(共带=两终点命中同一 Boss 行集,「走哪条路打谁」落空))
|
||||
var lv: LevelTable = loaded.get("level")
|
||||
var bg: BoardGenTable = loaded.get("board_gen")
|
||||
var en: EnemyTable = loaded.get("enemy")
|
||||
if lv != null and bg != null and en != null:
|
||||
var bg_keys := {}
|
||||
for row in bg.rows:
|
||||
bg_keys["%d_%d" % [int(row.level_index), int(row.node_depth)]] = true
|
||||
var en_bands := {} # "%d_%s" % [L, row_kind] → 升序带键数组
|
||||
for row in en.rows:
|
||||
var ek := "%d_%s" % [int(row.level_index), String(row.get("row_kind", ""))]
|
||||
if not en_bands.has(ek):
|
||||
en_bands[ek] = []
|
||||
var eb := int(row.get("depth_band", -1))
|
||||
if not (en_bands[ek] as Array).has(eb):
|
||||
(en_bands[ek] as Array).append(eb)
|
||||
for ek in en_bands.keys():
|
||||
(en_bands[ek] as Array).sort()
|
||||
for lrow in lv.rows:
|
||||
var li: int = int(lrow.level_index)
|
||||
# 模板池感知(关卡节点图生成 v0.4——池内全模板跨表校验,不只 row.nodes)
|
||||
var node_lists: Array[Array] = []
|
||||
var tpls: Array[Variant] = lrow.get("templates", [])
|
||||
if tpls.is_empty():
|
||||
node_lists.append(lrow.get("nodes", []))
|
||||
else:
|
||||
for tpl in tpls:
|
||||
node_lists.append(tpl.get("nodes", []))
|
||||
for tpl_i in range(node_lists.size()):
|
||||
var boss_bands := {} # 本图已占 Boss 带键
|
||||
for node in node_lists[tpl_i]:
|
||||
var t: String = String(node.get("type", ""))
|
||||
if t != "battle" and t != "boss":
|
||||
continue
|
||||
var nd: int = int(node.get("node_depth", -1))
|
||||
var key := "%d_%d" % [li, nd]
|
||||
if not bg_keys.has(key):
|
||||
push_diag.call("error", "level_node_dangling", "level %d %s 节点 (%s) 在 board_gen 无精确行" % [li, t, key])
|
||||
var nk := "%d_%s" % [li, t]
|
||||
if not en_bands.has(nk):
|
||||
push_diag.call("error", "level_node_dangling", "level %d %s 节点 (%d) 在 enemy 无该行类行集" % [li, t, nd])
|
||||
continue
|
||||
var hit_band: int = EnemyTable.band_of(en_bands[nk], nd)
|
||||
if hit_band < 0:
|
||||
push_diag.call("error", "level_node_dangling", "level %d %s 节点深度 %d 高于 (level %d, %s) 顶带键 %d——顶 clamp 消解" % [
|
||||
li, t, nd, li, t, int((en_bands[nk] as Array)[-1])])
|
||||
continue
|
||||
if t == "boss":
|
||||
if boss_bands.has(hit_band):
|
||||
push_diag.call("error", "rowset_rule", "level %d 模板 %d 多终点 Boss 节点同带(band %d)——共带使「走哪条路打谁」落空" % [li, tpl_i, hit_band])
|
||||
boss_bands[hit_band] = true
|
||||
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
|
||||
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")):
|
||||
|
||||
+71
-84
@@ -64,7 +64,7 @@ var _phase: int = Phase.IDLE
|
||||
var level_result := "" # "" / "victory" / "defeat"(外部契约面)
|
||||
var _candidates: Array[Dictionary] = [] # 当前选路候选(节点引用数组)
|
||||
var visited: Array[Dictionary] = [] # 已兑付/到达节点序列(呈现层图条数据源;含当前节点)
|
||||
var _graph_mode := false # 图形态缓存(行集任一节点带 next;单链 = false)
|
||||
var _generator # 参数化长图生成器(惰性建——NodeMapGenerator 持注入的 RngService)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
@@ -127,7 +127,7 @@ func restore_hero_base() -> void:
|
||||
|
||||
# ---- 关卡生命周期 ----
|
||||
|
||||
func start_level(level_index: int, seed_val: int, base_override_in: Dictionary = {}, template_pick: int = -1) -> bool:
|
||||
func start_level(level_index: int, seed_val: int, base_override_in: Dictionary = {}) -> bool:
|
||||
# 单关测试模式:私有账本、内置恢复数学、自调 new_adventure(漂移①
|
||||
# 单关=冒险的历史简化在测试模式保留;正式种子域归 AdventureRunner.start_adventure)
|
||||
# base_override_in = 永久强化 flat 注入尾参(缺省 {} 全零白板,
|
||||
@@ -146,34 +146,31 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
_phase = Phase.IDLE
|
||||
_candidates = []
|
||||
visited = []
|
||||
_graph_mode = false
|
||||
gp = 0 # 冒险钱包清零开局(局内货币生命周期起点)
|
||||
run_ledger.bonus = {"atk": 0, "max_hp": 0, "armor_class": 0} # 加算区冒险开始全 0(单关入口零元进度——注入面见上)
|
||||
base_override = {"atk": 0, "max_hp": 0, "armor_class": 0} # 永久强化 flat:缺省全零白板(单关入口零元进度——注入面见上)
|
||||
_shop_purchased = {} # 已购档数清零(跨冒险不延续——递增定价按冒险内累计)
|
||||
for k in base_override.keys():
|
||||
base_override[k] = maxi(0, int(base_override_in.get(k, 0))) # 注入规范化(契约同冒险开始参数面)
|
||||
for node in nodes():
|
||||
if node.has("next"):
|
||||
_graph_mode = true
|
||||
break
|
||||
hero_hp = effective_max_hp()
|
||||
rng.new_adventure(seed_val) # 关卡起点 = 冒险起点(切片口径;跨关接续归 冒险结构)
|
||||
level_row.assign(_resolve_template_row(row, template_pick)) # 池解析在播种后(node_map 抽签须已播种——EC7)
|
||||
_phase = Phase.RUNNING
|
||||
var entry := _entry_node()
|
||||
if entry.is_empty():
|
||||
push_error("[关卡节点地图] 入口节点缺失(无入边唯一节点——图合法性校验应已拦截)")
|
||||
if not _assign_generated_graph(row, seed_val):
|
||||
_phase = Phase.ENDED
|
||||
return false
|
||||
return _enter_node(entry)
|
||||
_phase = Phase.RUNNING
|
||||
var entries := _entry_nodes()
|
||||
if entries.is_empty():
|
||||
push_error("[关卡节点地图] 入口节点缺失(无入边节点集为空——validate_graph 应已拦)")
|
||||
_phase = Phase.ENDED
|
||||
return false
|
||||
return _enter_node(entries[0])
|
||||
|
||||
|
||||
# 冒险结构接口①:开始关卡(载荷 = 关卡序号 + 当前生命 + 生效上限——生命经共享账本交接;
|
||||
# 种子域归冒险结构:本路径不重播种;账本归冒险结构:本路径不重置 gp/bonus/_shop_purchased,
|
||||
# 也不重置 hero_hp——账本当前值即开局生命,AdventureRunner 已在 ledger 写好)。
|
||||
# base_override = 永久强化 flat 共享引用(缺省 {} 全零白板,与 B1 空参数路径同值)。
|
||||
# 流程与 start_level 同款(_find_level_row → 状态初始化 → _graph_mode 探测 → _enter_node(entry)),
|
||||
# 流程与 start_level 同款(_find_level_row → 生成图装配 → 状态初始化 → _enter_node(入口)),
|
||||
# 差异仅在账本/加算区/已购档/恢复执行体的注入与零重置。
|
||||
# defer_entry 尾参(缺省 false = 直接进节点)——true 时不自动
|
||||
# _enter_node(entry),改置选路态(awaiting_choice + 单候选 = 入口节点),由 AdventureRunner 在
|
||||
@@ -183,7 +180,7 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
# _enter_node(entry)——测量面零漂移红线。
|
||||
func start_level_external(level_index: int, ledger: Dictionary,
|
||||
purchased_ref: Dictionary, rest_handler: Callable, base_override_ref: Dictionary = {},
|
||||
defer_entry: bool = false, template_pick: int = -1, resume_state: Dictionary = {}) -> bool:
|
||||
defer_entry: bool = false, resume_state: Dictionary = {}) -> bool:
|
||||
var row := _find_level_row(level_index)
|
||||
if row.is_empty():
|
||||
push_error("[关卡节点地图] level_index %d 在 LevelTable 无行" % level_index)
|
||||
@@ -193,34 +190,30 @@ func start_level_external(level_index: int, ledger: Dictionary,
|
||||
base_override = base_override_ref
|
||||
_shop_purchased = purchased_ref
|
||||
_rest_handler = rest_handler
|
||||
level_row.assign(_resolve_template_row(row, template_pick))
|
||||
if not _assign_generated_graph(row, rng.master_seed):
|
||||
return false
|
||||
node_index = -1
|
||||
level_result = ""
|
||||
_phase = Phase.IDLE
|
||||
_candidates = []
|
||||
visited = []
|
||||
_graph_mode = false
|
||||
for node in nodes():
|
||||
if node.has("next"):
|
||||
_graph_mode = true
|
||||
break
|
||||
_phase = Phase.RUNNING
|
||||
# 断点续玩路径(defer_entry 之外的第三种停点入口):直接重建到停点所在节点,
|
||||
# 零 RNG 消耗(模板已随 template_pick 强制、节点状态纯重建不 _enter_node——当场战斗从头打)。
|
||||
# 零 RNG 消耗(图由主种子+参数档重生成同图、节点状态纯重建不 _enter_node——当场战斗从头打)。
|
||||
# resume_state = {"node_id": int(-1 = 入口停点), "visited_ids": Array[int]};空字典 = 既有路径逐位不变。
|
||||
if not resume_state.is_empty():
|
||||
return _restore_to_stop(int(resume_state.get("node_id", -1)),
|
||||
resume_state.get("visited_ids", []) as Array)
|
||||
var entry := _entry_node()
|
||||
if entry.is_empty():
|
||||
push_error("[关卡节点地图] 入口节点缺失(无入边唯一节点——图合法性校验应已拦截)")
|
||||
var entries := _entry_nodes()
|
||||
if entries.is_empty():
|
||||
push_error("[关卡节点地图] 入口节点缺失(无入边节点集为空——validate_graph 应已拦)")
|
||||
_phase = Phase.ENDED
|
||||
return false
|
||||
if defer_entry:
|
||||
_phase = Phase.AWAITING_CHOICE # 入口停点(node_index 保持 -1——进节点前无 current_node)
|
||||
_candidates = [entry]
|
||||
_phase = Phase.AWAITING_CHOICE # 入口停点(node_index 保持 -1)——候选=全部入口(多入口首跳即选路)
|
||||
_candidates = entries
|
||||
return true
|
||||
return _enter_node(entry)
|
||||
return _enter_node(entries[0])
|
||||
|
||||
|
||||
# 入口停点呈现(defer_entry=true 的第二步):发出入口候选供呈现层弹
|
||||
@@ -240,14 +233,14 @@ func present_entry_choice() -> bool:
|
||||
# 候选 = 停点节点出边(与 choices_presented 停点语义逐位一致)。
|
||||
func _restore_to_stop(node_id: int, visited_ids: Array) -> bool:
|
||||
if node_id < 0:
|
||||
# 入口停点:与 defer_entry 同款形态(node_index 保持 -1,单候选 = 入口节点)
|
||||
var entry := _entry_node()
|
||||
if entry.is_empty():
|
||||
# 入口停点:与 defer_entry 同款形态(node_index 保持 -1,候选 = 全部入口)
|
||||
var entries := _entry_nodes()
|
||||
if entries.is_empty():
|
||||
push_error("[关卡节点地图] 断点恢复失败——入口节点缺失")
|
||||
_phase = Phase.ENDED
|
||||
return false
|
||||
_phase = Phase.AWAITING_CHOICE
|
||||
_candidates = [entry]
|
||||
_candidates = entries
|
||||
return true
|
||||
var stop: Dictionary[Variant, Variant] = {}
|
||||
for node in nodes():
|
||||
@@ -296,32 +289,31 @@ func _find_level_row(level_index: int) -> Dictionary:
|
||||
return db.level_row(level_index) # 表访问收口(行集直扫归 LevelTable/ConfigDB 访问器)
|
||||
|
||||
|
||||
# ---- 模板池解析(关卡节点图生成 v0.4——M-3)----
|
||||
# row.templates 非空 = 池形态:template_pick >= 0 = 强制取该模板(首次脚本模式/冒烟固定图,零 RNG 消费);
|
||||
# -1 = 抽签(node_map 用途标签恰一 scope,均匀取模)。空池 = 旧单图形态原样返回(向后兼容零迁移)。
|
||||
# 解析产物 = row 副本换 nodes(下游 nodes()/校验/呈现全透明);active_template_name 供 MC 分层出数;
|
||||
# active_template_index 供断点快照备案(已抽模板续玩零重抽零 RNG 消耗)。
|
||||
var active_template_name := ""
|
||||
var active_template_index := -1 # -1 = 非池形态(旧单图)/未解析
|
||||
|
||||
func _resolve_template_row(row: Dictionary, template_pick: int) -> Dictionary:
|
||||
var templates: Array[Variant] = row.get("templates", [])
|
||||
active_template_name = ""
|
||||
active_template_index = -1
|
||||
if templates.is_empty():
|
||||
return row
|
||||
var idx := 0
|
||||
if template_pick >= 0:
|
||||
idx = clampi(template_pick, 0, templates.size() - 1)
|
||||
else:
|
||||
var stream: RandomNumberGenerator = rng.open_stream("node_map")
|
||||
idx = stream.randi_range(0, templates.size() - 1)
|
||||
var tpl: Dictionary[Variant, Variant] = templates[idx]
|
||||
active_template_name = String(tpl.get("display_name", ""))
|
||||
active_template_index = idx
|
||||
var out := row.duplicate(true)
|
||||
out["nodes"] = tpl.get("nodes", [])
|
||||
return out
|
||||
# ---- 图源装配(关卡节点图生成 v1.1——图 = f(参数档, 种子) 纯函数;无模板池、无兜底图)----
|
||||
# 参数档按关卡序号解析(消费侧职责——生成器不认关卡号),种子取本局口径:单关模式用传入种子、
|
||||
# 冒险模式用主种子(产图与全局计数器号位无关,断点在停点重生成同图)。产图先过消费侧独立校验
|
||||
# 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)
|
||||
if params.is_empty():
|
||||
push_error("[关卡节点地图] level %d 无参数档行(level_gen_params 缺行——配置错误)" % level_index)
|
||||
return false
|
||||
if _generator == null:
|
||||
_generator = NodeMapGenerator.new(rng)
|
||||
var graph: Dictionary[Variant, Variant] = _generator.generate(params, seed_val)
|
||||
if graph.is_empty() or _generator.is_fatal():
|
||||
push_error("[关卡节点地图] level %d 生成图失败(参数无解或产出不合格——无兜底图)" % level_index)
|
||||
return false
|
||||
var bad := LevelTable.validate_graph(graph["nodes"] as Array)
|
||||
if not bad.is_empty():
|
||||
for e in bad:
|
||||
push_error("[关卡节点地图] level %d 生成图违规: %s" % [level_index, String(e["message"])])
|
||||
return false
|
||||
var merged := row.duplicate(true)
|
||||
merged["nodes"] = graph["nodes"]
|
||||
level_row.assign(merged)
|
||||
return true
|
||||
|
||||
|
||||
func nodes() -> Array:
|
||||
@@ -334,37 +326,30 @@ func current_node() -> Dictionary:
|
||||
return nodes()[node_index]
|
||||
|
||||
|
||||
# ---- 图版行进面(v1.0:出边表选路 · 零自动推进 · 单链 = 隐式出边退化形态)----
|
||||
# 编号空间(与 LevelTable 校验器同一解析):图形态 = node_depth(全节点编号化);单链 = 数组下标。
|
||||
# 混用两空间会在单链上碰撞(如 reward 下标 2 vs 战斗 node_depth 2)——节点寻址一律经 _node_id。
|
||||
# ---- 图版行进面(生成图恒带显式出边;单链退化形态 = 出度 ≤1 的图,无数组序隐式边)----
|
||||
# 节点身份=生成图句柄 id(图内唯一)。定位信息另算(enc 携带关卡序号 + 节点深度),
|
||||
# 禁再以深度当身份(句柄/位置两轴语义单一)。
|
||||
|
||||
func _node_id(node: Dictionary) -> int:
|
||||
if _graph_mode:
|
||||
return int(node.get("node_depth", -1))
|
||||
return nodes().find(node)
|
||||
return int(node.get("id", -1))
|
||||
|
||||
|
||||
# 入口节点 = 无入边唯一节点(图合法性校验保证恰一)
|
||||
func _entry_node() -> Dictionary:
|
||||
# 入口节点集 = 全部无入边节点(个数=参数档 entry_count;多入口时首跳即选路)
|
||||
func _entry_nodes() -> Array[Dictionary]:
|
||||
var inbound := {}
|
||||
for node in nodes():
|
||||
for tid in _out_ids(node):
|
||||
inbound[tid] = true
|
||||
inbound[int(tid)] = true
|
||||
var out: Array[Dictionary] = []
|
||||
for node in nodes():
|
||||
if not inbound.has(_node_id(node)):
|
||||
return node
|
||||
return {}
|
||||
out.append(node)
|
||||
return out
|
||||
|
||||
|
||||
# 出边编号表:显式 next 优先;单链(无 next 键)= 数组顺序下一项(退化形态)
|
||||
# 出边句柄表(生成图恒显式 next)
|
||||
func _out_ids(node: Dictionary) -> Array:
|
||||
if node.has("next"):
|
||||
return node.get("next", [])
|
||||
var nodes: Array[Variant] = nodes()
|
||||
var i := nodes.find(node)
|
||||
if i >= 0 and i < nodes.size() - 1:
|
||||
return [i + 1]
|
||||
return []
|
||||
return node.get("next", [])
|
||||
|
||||
|
||||
# 出边候选(节点引用数组——选路口/呈现层共用读面)
|
||||
@@ -442,11 +427,13 @@ func type_visible_ids() -> Array[int]:
|
||||
# 不可达节点不落表(加载期孤儿校验应已拦截)。
|
||||
func _bfs_layers() -> Dictionary:
|
||||
var layers := {}
|
||||
var entry := _entry_node()
|
||||
if entry.is_empty():
|
||||
var entries := _entry_nodes()
|
||||
if entries.is_empty():
|
||||
return layers
|
||||
var queue: Array[Dictionary] = [entry]
|
||||
layers[_node_id(entry)] = 0
|
||||
var queue: Array[Dictionary] = []
|
||||
for entry in entries:
|
||||
layers[_node_id(entry)] = 0
|
||||
queue.append(entry)
|
||||
var i := 0
|
||||
while i < queue.size():
|
||||
var cur: Dictionary[Variant, Variant] = queue[i]
|
||||
@@ -510,7 +497,7 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
# node_entered 先于 begin_battle:呈现层须先拿到 settlement 引用,才能响应 begin 期间发出的
|
||||
# board_changed(首板安装)并重建棋盘——后发会导致首战棋盘不渲染
|
||||
node_entered.emit(node, {})
|
||||
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)),
|
||||
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("depth", 0)),
|
||||
"hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(),
|
||||
"hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(),
|
||||
"attack_type": _hero_attack_type, "armor_type": _hero_armor_type, # 两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退)
|
||||
@@ -523,7 +510,7 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
"voucher_rap": _voucher_rap,
|
||||
"voucher_time_sand": _voucher_time_sand,
|
||||
"node_type": String(node.get("type", ""))}):
|
||||
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
|
||||
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("depth", -1)))
|
||||
return false
|
||||
_voucher_rap = 0 # 节点级一次性:注入即消费(战斗未启动则保留至下次注入前不清)
|
||||
_voucher_time_sand = 0
|
||||
@@ -535,7 +522,7 @@ func _forward_step_row(row: Dictionary) -> void:
|
||||
|
||||
|
||||
# battle_index=第几场战斗(关卡域 1 基序——visited 已含当前节点〔_enter_node :437 先追加后启动〕;
|
||||
# 按已到节点 type=battle 计数,与图形态 node_depth/单链数组下标两编号空间解耦〔混编碰撞免疫〕;
|
||||
# 按已到节点 type=battle 计数,与句柄 id / 节点深度两轴解耦〔身份与位置互不兼任〕;
|
||||
# 开局姿态组「每关首轮窗」条件面)
|
||||
func _battle_index() -> int:
|
||||
var n := 0
|
||||
|
||||
@@ -299,9 +299,10 @@ func _default_data() -> Dictionary[String, Variant]:
|
||||
}
|
||||
|
||||
|
||||
# ---- 首次冒险布尔位(关卡节点图生成 v0.4)----
|
||||
# false = 首次脚本模式(全关零抽签取池内模板 0=现行固定图);置位只在冒险收束挂点(MetaProgression.settle_adventure)。
|
||||
# 老档迁移:缺字段 = 该档已有游玩史 → true(不重放教学脚本)。
|
||||
# ---- 首次冒险布尔位(关卡节点图生成 v1.1 确定性承载)----
|
||||
# false = 本档尚无游玩史 → 首局走固定种子 42 + 参数档生成图(教学与存档复现的固定图由此承载,无兜底模板);
|
||||
# 置位只在冒险收束挂点(MetaProgression.settle_adventure)。
|
||||
# 老档迁移:缺字段 = 该档已有游玩史 → true(不重放首局固定图)。
|
||||
|
||||
func is_first_adventure_done() -> bool:
|
||||
return bool(_data["meta"].get("first_adventure_done", true))
|
||||
|
||||
Reference in new issue
Block a user