refactor(迭代35卡②T2·二口+T3 S1/S2/S6/S8): 消费侧切源落定+断言面换代主体——① level_runner 图源换代(_generator 取代模板解析,_assign_generated_graph 读 level_gen_params 参数档产图并过 LevelTable.validate_graph,违规起关拒绝零兜底)+_entry_nodes 多入口首跳+_node_id 句柄单空间+enc node_depth 取节点 depth 值;② adventure_runner/main/save_manager 模板链路退役(first_script 参数与 template_pick 计算全删,断点 template_pick 键换生成图代差标记键 graph_gen_v2,缺键旧断点 resume 显式拒绝禁静默回放);③ level.tres 三行 templates 键清出(15 张模板数据出表,行级字段留,表尾注释改指参数档+生成器);④ sim/probe 随动(run_level/run_adventure 尾参退役、MC 出数字段 level_templates→level_maps 值=关卡#主种子、探针注释性质化、boss_depth 取节点 depth 键);⑤ 断言面换代:node_map_templates 宿主整段重写为生成图确定性套(纯函数复现/换种子变图/零计数器号位依赖/单一图源/种子 42 两开局同图/断点重生成同图/缺代差键拒绝/表无 templates/收束置位三条保留),breakpoint_resume 新增 P7 六断言(快照无已抽模板键、代差标记、停点句柄与已到序往返、续玩同图、旧断点拒绝、同种子重开局同图),S8 单测随动 first_script 键清退+断点夹具换代+meta 参数面 extra 键样换 treasure_unlocked(语义保持),S2 生成图取数面三宿主替换(level_active_nodes 走参数档+种子 42,消费点深度键换 depth,round_flow 死件 _boss_depth 随删),adventure_feats 覆盖义务段改真实解析链判据(敌侧落带≥请求深度+板侧命中+非空扫描面)、奖励节点段换「图侧含件+真实选路点选呈停兑付」双断言(旧单链必经形状前提随模板消亡,非放宽:经停即呈候选由真实 pick_next_node 入口证明)、商店余额段按每关恰一店投影改三关读数
This commit is contained in:
1 parent
6566200220
commit
9ee3bea194
22 files changed
+460
-362
No files matched your search
+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 = 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 = _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
|
||||
|
||||
Reference in new issue
Block a user