迭代11第①批:魔法道具同调实施批 T0-T10 全落(重闸 645/0 + MC off 逐位 PASS)
- T1 表与schema:attunement_growth.tres 谓词表(m1/m2/m3+Σgrant≤slot_cap 校验+slots() 求值口径)+道具表扩列(form/charges/slot_cost/unlock_requires/effective_rounds/charge_n+effect_kinds 九kind+KEY_WHITELIST 零裸flat护栏)+七同调行 - T2 save v4(boss_kill_count 收束入账+attunement_equipped+attune_refresh_level+断点 attunement_charges+迁移链 v3→v4) - T3 Attunement 求值服务(纯函数零 await 零 RNG) - T4 营地装配覆盖层(工坊扩容程序占位+Y 升级行 attune_refresh_upgrade 行族) - T5 被动六件接线+装配注入链+刷新 X/Y 双源(BonusPickOverlay 刷新钮,autoplay 默认不刷) - T6 掘进蓄能全链+摆钟能力面(骑库存面差额注入+断点充能键);炸格 P5 甲影响评估破面大→用户裁拆后批,blast_kit deferred 留位 - T7 退役面五件(BONUS_IDS 除名 aux_item+transitional_sync_slots 过渡键退役=迭代10欠账③清偿+授予路/四容量闸拆除+nest_pendulum 换装 active_charged) - T8 冒烟六新断言(645/0)+MC attune 开关+贪心画像分层 off/on 各 N=60 双策略(on 画像功率显著带平移归调参批;off 新锚=对拍正主留盘主区 sim-data,81000 降批入口基准) - T9 连带修订九件(魔法道具定位重写/元进度v1.6/地图战斗资源v0.16/行动经济v0.7/05不做v1.3/关卡节点地图v1.4/node-reward v2.2/registry 三处/06 收口+炸格拆后批新条) - T10 重闸全绿:冒烟 645/0+熵增 PASS+doc 三闸零新增+MC off 复跑同种子逐位一致 PASS;出口人闸后移迭代 12(用户 2026-10-03 令)
This commit is contained in:
1 parent
02cb92a972
commit
a6a2bf885e
37 files changed
+1075
-173
No files matched your search
+105
-33
@@ -72,6 +72,10 @@ 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;全零 = 白板)
|
||||
var _last_aux_cleared := 0 # additive(迭代 7 T4——最近一次关卡末清空计数,summary 取数面)
|
||||
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——params.aux_unlocked 接收面)
|
||||
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——params.attunement 接收面,start_level 外下传 LevelRunner)
|
||||
var _attune_refresh_x := 0 # 刷新件余量(迭代 11 第①批 T5b P1 甲——每碰三选一重置=X 表值;优先消耗、不结转)
|
||||
var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值)
|
||||
var attune_charges := {} # 同调主动件冒险充能账本(迭代 11 第①批 T6——跨关卡共享 dict;settlement use_aux 扣减直改同引用;断点随 attunement_charges 键入档)
|
||||
var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省)
|
||||
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
|
||||
# ---- 三选一停点态(迭代 10 奖励重构批)----
|
||||
@@ -112,7 +116,13 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
||||
_pending_next_level = false
|
||||
_base_override = _normalize_override(params.get("hero_override", {}))
|
||||
_first_script = bool(params.get("first_script", false)) # 缺省 = 非首次(抽签路径;真实侧由呈现层读存档布尔位传入)
|
||||
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {} # additive(迭代 7 T5)
|
||||
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义) # additive(迭代 7 T5)
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||
ledger["hero_hp"] = effective_max_hp() # 生命账本置满(生效上限口径——T17 修复:含 TunePanel pct,与战斗内同源)
|
||||
state = "running"
|
||||
return _start_current_level()
|
||||
@@ -168,6 +178,9 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
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 # 续玩模板已随快照强制(已抽模板零重抽)——脚本模式不重放
|
||||
_aux_unlocked = params.get("aux_unlocked", {}) if params.get("aux_unlocked", {}) is Dictionary else {}
|
||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive(迭代 11 第①批 T5)
|
||||
_attune_refresh_y = maxi(0, int(params.get("attune_refresh_y", 0))) # additive(迭代 11 第①批 T5b——Y 冒险周期池)
|
||||
_seed_attune_charges() # 迭代 11 第①批 T6——冒险开始充能账本按装备主动件重置(每冒险充能语义)
|
||||
# 三选一停点续玩分支(迭代 10——选择态/已兑付态不重跑关卡;奖励选择态不重抽、已兑付不重付)
|
||||
var bp_pending: Dictionary = bp.get("bonus_pending", {}) as Dictionary
|
||||
var bp_result: Dictionary = bp.get("bonus_result", {}) as Dictionary
|
||||
@@ -197,6 +210,10 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
bonus_resolved.emit(_bonus_result.duplicate(true))
|
||||
return true
|
||||
_resume_bp = bp.duplicate(true)
|
||||
# 迭代 11 第①批 T6——充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||
var bp_charges = bp.get("attunement_charges", null)
|
||||
if bp_charges is Dictionary:
|
||||
attune_charges = (bp_charges as Dictionary).duplicate(true)
|
||||
state = "running"
|
||||
return _start_current_level()
|
||||
|
||||
@@ -315,15 +332,11 @@ func _pick_aux_item(count: int) -> bool:
|
||||
push_error("[冒险结构] pick_bonus(aux_item) 拒绝——解锁道具池空集(白板/门控态不入牌库的防御兜底)")
|
||||
return false
|
||||
var hp_before := int(ledger.get("hero_hp", 0))
|
||||
if AuxItems.can_accept(picked.size(), _bonus_aux_carry.size(), db): # 同调位守卫单点(2026-10-02)
|
||||
for iid in picked:
|
||||
_bonus_aux_carry.append(String(iid))
|
||||
_finish_bonus_pick("aux_item", picked.size(), hp_before)
|
||||
return true
|
||||
# 满位替换子交互:现有持有件 + 新件 N+1 选 N(丢旧收新 / 放弃新件两路,终态恒 ≤ 同调位)
|
||||
_bonus_pending["chosen"] = "aux_item"
|
||||
_bonus_pending["incoming"] = picked.duplicate()
|
||||
aux_replace_presented.emit(_carry_records(), {"index": -1, "item_id": String(picked[0]), "count": picked.size()})
|
||||
# 迭代 11 第①批 T7——同调位守卫随过渡键退役拆除(aux 卡出池后本路径不可达;能力件容量闸
|
||||
# 正主 = 营地装配面 Attunement.slots 满位禁选)
|
||||
for iid in picked:
|
||||
_bonus_aux_carry.append(String(iid))
|
||||
_finish_bonus_pick("aux_item", picked.size(), hp_before)
|
||||
return true
|
||||
|
||||
|
||||
@@ -372,6 +385,7 @@ func node_bonus_candidates() -> Dictionary:
|
||||
|
||||
|
||||
func present_node_bonus(candidates: Array, aux_detail: Array = []) -> void:
|
||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||
_node_bonus_pending = {"candidates": candidates.duplicate(true), "aux_detail": aux_detail.duplicate(true)}
|
||||
|
||||
|
||||
@@ -405,18 +419,14 @@ func _apply_node_aux(count: int) -> bool:
|
||||
var records: Array = []
|
||||
if _runner != null and _runner.aux_inventory != null:
|
||||
records = _runner.aux_inventory.holding_records()
|
||||
var holding := records.size() + _bonus_aux_carry.size()
|
||||
if AuxItems.can_accept(picked.size(), holding, db): # 同调位守卫单点(2026-10-02)
|
||||
if _runner != null:
|
||||
for iid in picked:
|
||||
var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node")
|
||||
if rec.is_empty():
|
||||
push_warning("[冒险结构] 奖励节点道具授予被拒(防御兜底留痕): %s" % String(iid))
|
||||
_finish_node_bonus("aux_item", picked.size())
|
||||
return true
|
||||
_node_bonus_pending["chosen"] = "aux_item"
|
||||
_node_bonus_pending["incoming"] = picked.duplicate()
|
||||
aux_replace_presented.emit(records, {"handle": -1, "item_id": String(picked[0]), "count": picked.size()})
|
||||
# 迭代 11 第①批 T7——守卫随过渡键退役拆除(BONUS_IDS 已除名 aux_item,本分支不可达;
|
||||
# 替换子交互路由随容量闸一并退役——能力件容量闸正主 = 营地装配面满位禁选)
|
||||
if _runner != null:
|
||||
for iid in picked:
|
||||
var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node")
|
||||
if rec.is_empty():
|
||||
push_warning("[冒险结构] 奖励节点道具授予被拒(防御兜底留痕): %s" % String(iid))
|
||||
_finish_node_bonus("aux_item", picked.size())
|
||||
return true
|
||||
|
||||
|
||||
@@ -436,6 +446,71 @@ func _resolve_node_replace(discard_handle: int) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# 冒险开始充能账本重置(T6——装备 active_charged 件各回 charges_per_adventure;被动件不入账)
|
||||
func _seed_attune_charges() -> void:
|
||||
attune_charges = {}
|
||||
for iid in _attunement:
|
||||
var arow: Dictionary = db.aux_item_row(String(iid))
|
||||
if String(arow.get("form", "")) == "active_charged":
|
||||
attune_charges[String(iid)] = maxi(0, int(arow.get("charges_per_adventure", 0)))
|
||||
|
||||
|
||||
# ---- 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源:X=刷新件每碰三选一重给〔优先消耗、不结转〕、
|
||||
# Y=营地升级行冒险周期池〔用完即无〕;重抽消费 reward_pick 既有洗牌流,池规则全保留——不放回/
|
||||
# 达上限剔牌/掷定前置直显;停点可中断)----
|
||||
|
||||
func _attune_refresh_x_total() -> int:
|
||||
return int(db.aux_item_row("pick_refresh").get("magnitude", 0)) if _attunement.has("pick_refresh") else 0
|
||||
|
||||
|
||||
func _reset_refresh_x() -> void:
|
||||
_attune_refresh_x = _attune_refresh_x_total()
|
||||
|
||||
|
||||
# 当前可刷新次数(X 优先 + Y 池——呈现层余量读数口)
|
||||
func attune_refresh_left() -> int:
|
||||
return _attune_refresh_x + _attune_refresh_y
|
||||
|
||||
|
||||
# 节点停点在途态读回(组合根重 present 用——candidates/aux_detail 值拷贝)
|
||||
func node_bonus_pending_state() -> Dictionary:
|
||||
return {
|
||||
"candidates": (_node_bonus_pending.get("candidates", []) as Array).duplicate(true),
|
||||
"aux_detail": (_node_bonus_pending.get("aux_detail", []) as Array).duplicate(true),
|
||||
}
|
||||
|
||||
|
||||
# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
||||
# bonus_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_bonus_pending_state 重 present。
|
||||
func refresh_bonus() -> bool:
|
||||
if _attune_refresh_x <= 0 and _attune_refresh_y <= 0:
|
||||
return false
|
||||
if not _bonus_pending.is_empty():
|
||||
if _attune_refresh_x > 0:
|
||||
_attune_refresh_x -= 1
|
||||
else:
|
||||
_attune_refresh_y -= 1
|
||||
var cands: Array = _draw_bonus_candidates()
|
||||
_bonus_pending["candidates"] = cands.duplicate(true)
|
||||
_bonus_pending["aux_detail"] = _last_draw_aux.duplicate()
|
||||
var payload: Dictionary = (_bonus_pending.get("payload", {}) as Dictionary).duplicate(true)
|
||||
payload["candidates"] = cands
|
||||
payload["aux_detail"] = _last_draw_aux.duplicate()
|
||||
bonus_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途)
|
||||
_snapshot_breakpoint()
|
||||
return true
|
||||
if not _node_bonus_pending.is_empty():
|
||||
if _attune_refresh_x > 0:
|
||||
_attune_refresh_x -= 1
|
||||
else:
|
||||
_attune_refresh_y -= 1
|
||||
var cands2: Array = _draw_bonus_candidates()
|
||||
_node_bonus_pending["candidates"] = cands2.duplicate(true)
|
||||
_node_bonus_pending["aux_detail"] = _last_draw_aux.duplicate()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
# 替换在途守卫(2026-10-02 修复批——双渠道替换受理的共用前置谓词:停点态非空且 chosen=aux_item)
|
||||
func _replace_in_transit(pending: Dictionary) -> bool:
|
||||
return not pending.is_empty() and String(pending.get("chosen", "")) == "aux_item"
|
||||
@@ -501,12 +576,8 @@ func _draw_bonus_candidates() -> Array:
|
||||
keyed.sort_custom(func(a, b): return float(a["key"]) > float(b["key"]))
|
||||
var out: Array = []
|
||||
for i in mini(int(db.adventure.bonus_candidate_count), keyed.size()): # 候选数读表(2026-10-02 魔法数结构化——默认 3 = 现行行为)
|
||||
var cid := String(keyed[i]["id"])
|
||||
out.append(cid)
|
||||
if cid == "aux_item":
|
||||
var roll_rg: RandomNumberGenerator = rng.open_stream("reward_pick")
|
||||
for j in maxi(1, int(db.adventure.bonus_aux_item_count)):
|
||||
_last_draw_aux.append(_roll_from(roll_rg))
|
||||
out.append(String(keyed[i]["id"]))
|
||||
# 迭代 11 第①批 T7——aux_item 掷定前置分支随池种退役拆除(BONUS_IDS 已除名,候选恒无 aux 卡)
|
||||
return out
|
||||
|
||||
|
||||
@@ -592,11 +663,10 @@ func _start_current_level() -> bool:
|
||||
var ok: bool = _runner.start_level_external(level_index, ledger, bonus, purchased,
|
||||
Callable(self, "apply_rest"), _base_override, true, template_pick, resume_state)
|
||||
if ok:
|
||||
# 三选一道具件跨关携带注入(迭代 10——上关兑付的道具件进本关库存〔奖励只朝下一战生效〕;
|
||||
# 注入即移交——carry 清空,本关末随关卡域库存清空)
|
||||
for iid in _bonus_aux_carry:
|
||||
_runner.grant_aux_item(iid, "bonus_pick")
|
||||
_bonus_aux_carry = []
|
||||
_runner.set_attunement(_attunement) # 迭代 11 第①批 T5——同调装配下传(enc 携带进战斗结算)
|
||||
_runner.set_attune_charges(attune_charges) # 迭代 11 第①批 T6——充能账本下传(能力件骑库存面注入)
|
||||
# 三选一道具件跨关携带注入(迭代 10)——迭代 11 第①批 T7 退役:aux 卡出池后 carry 恒空集,
|
||||
# 注入面随停点渠道退役拆除(容器字段保留=替换子交互机械惰性承载,零投放零行为)
|
||||
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
||||
# 入口停点断点(node_id=-1)与 defer_entry 同态(node_index=-1+单候选)——走入口呈现;
|
||||
# 中途停点(node_id>=0)走停点呈现(restore_aux_items 先于呈现=C 区库存就位)。
|
||||
@@ -635,6 +705,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
ledger["hero_hp"] = hp_before + heal
|
||||
# 置位先于 emit(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位)
|
||||
_pending_next_level = true
|
||||
_reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转)
|
||||
var pick_payload: Dictionary = {
|
||||
"level_index": level_index,
|
||||
"candidates": _draw_bonus_candidates(),
|
||||
@@ -724,6 +795,7 @@ func _snapshot_breakpoint() -> void:
|
||||
},
|
||||
"gp": int(ledger.get("gp", 0)),
|
||||
"shop_purchased": purchased.duplicate(true),
|
||||
"attunement_charges": attune_charges.duplicate(true), # 迭代 11 第①批 T6——同调主动件充能余量随断点(save v4 域)
|
||||
"template_pick": _runner.active_template_index if _runner != null else -1,
|
||||
"master_seed": master_seed,
|
||||
"rng_counter": rng.counter(),
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Validation Question: 同调位读数是否由「通关次数 + Boss 击杀数」经 attunement_growth.tres 谓词表
|
||||
# 实时求值(Σ达成 grant clamp slot_cap),迭代 10 过渡键 transitional_sync_slots 得以退役(P0)
|
||||
# Date: 2026-10-03(迭代 11 第①批 魔法道具同调实施批 T3——任务书 v1.1 §六 T3;设计权威底稿 proposal §2)
|
||||
# 同调位求值服务:计数源组装(save 账本 → 计数字典)与实时求值,纯函数零副作用。
|
||||
# 读数源切换(迭代 10 欠账③):装配面消费方(营地装配覆盖层 T4/冒险开始装配注入 T6)一律经本服务取
|
||||
# Slots;过渡键 adventure.transitional_sync_slots 的在途读取面(aux_items 持有上限 gate/战斗内沉没/
|
||||
# 停点替换交互/main 同调位呈现)系过渡渠道承载——T7 退役面切换时随渠道一并消亡(该批读数源切换的
|
||||
# 正主落点,06 欠账③销账注记随 T7/T9)。过渡期切早 = off 态 MC 对 81000 锚添新扰动(fresh 档计数全零
|
||||
# → Slots=0 ≠ 过渡键 1),违「attune=off 逐位一致」🔴 契约——故本服务先立、在途面 T7 切。
|
||||
# attune=off 纪律:零 await、零 RNG、零写副作用(🔴 契约 3 的服务侧承载)。
|
||||
class_name Attunement
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# 计数源组装(save = SaveManager 或 null——模拟/冒烟未注入档,按全零计数求值)
|
||||
static func counters_from_save(save) -> Dictionary:
|
||||
if save == null:
|
||||
return {}
|
||||
return save.attunement_counters()
|
||||
|
||||
|
||||
# 同调位实时求值(营地进冒险前求值一次 + 装配覆盖层实时呈现共用同一口径)
|
||||
static func slots(db, save) -> int:
|
||||
if db == null or db.attunement_growth == null:
|
||||
return 0
|
||||
return db.attunement_growth.slots(counters_from_save(save))
|
||||
@@ -0,0 +1 @@
|
||||
uid://bwccv5x51gs23
|
||||
@@ -73,13 +73,9 @@ func holding_count() -> int:
|
||||
return n
|
||||
|
||||
|
||||
# 同调位守卫谓词(2026-10-02 修复批——四处手抄收口单点:战斗结算授予路由 / 关卡授予 / 三选一双渠道):
|
||||
# holding = 调用方渠道的当前实际持有数(库存渠道传 holding_count()、通关渠道传跨关携带数、
|
||||
# 节点渠道传库存+携带合计);读数源 = db.adventure.transitional_sync_slots(负值钳 0 口径同前)。
|
||||
# 返回再入 n 件是否仍在位内。挂 static:谓词纯算术不依赖实例态——通关渠道(跨关携带)没有
|
||||
# AuxItems 实例可用,实例方法形态不成立;db 参数不标类型(同 _db 桩表注入纪律)。
|
||||
static func can_accept(n: int, holding: int, db) -> bool:
|
||||
return holding + n <= maxi(0, int(db.adventure.transitional_sync_slots))
|
||||
# 同调位守卫谓词——迭代 11 第①批 T7 退役(读取源 transitional_sync_slots 过渡键已随表退役;
|
||||
# 容量闸正主 = 营地装配面 Attunement.slots 满位禁选。函数体保留空壳防四散引用误删遗漏——
|
||||
# T8 冒烟「过渡键读取点归零」断言覆盖)
|
||||
|
||||
|
||||
# 持有件记录(迭代 10 追加批——奖励节点替换子交互数据源:只列未消耗件,handle 定位丢弃目标)
|
||||
@@ -108,7 +104,7 @@ func _usable_now(item_id: String, board_has_preview_info: Callable) -> bool:
|
||||
if row.is_empty():
|
||||
return false
|
||||
var kinds: Array = row.get("effect_kinds", [])
|
||||
if kinds.has("mine_reveal") or kinds.has("ap_grant"):
|
||||
if kinds.has("mine_reveal") or kinds.has("ap_grant") or kinds.has("ap_charge_safe"):
|
||||
return true
|
||||
return bool(board_has_preview_info.call()) if board_has_preview_info.is_valid() else true
|
||||
|
||||
|
||||
@@ -67,6 +67,24 @@ var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位
|
||||
var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
|
||||
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
|
||||
var _flag_consumed := {} # 兑付资格以雷格实例为键(板级;换图重置)
|
||||
# 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
|
||||
var attunement: Array = [] # 装备中同调件 item_id 集
|
||||
var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_aux 消费直改)
|
||||
var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点)
|
||||
var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数
|
||||
|
||||
|
||||
func _attune_has(item_id: String) -> bool:
|
||||
return attunement.has(item_id)
|
||||
|
||||
|
||||
# 装备件表值读取(magnitude/effective_rounds 等——AuxItemTable 行;未装配不读表)
|
||||
func _attune_row(item_id: String) -> Dictionary:
|
||||
return db.aux_item_row(item_id) if _attune_has(item_id) else {}
|
||||
|
||||
|
||||
func _attune_mag(item_id: String) -> int:
|
||||
return int(_attune_row(item_id).get("magnitude", 0))
|
||||
var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次)
|
||||
var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置)
|
||||
var _perfect_hit_false := 0
|
||||
@@ -129,12 +147,23 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
if not _install_new_board():
|
||||
return false
|
||||
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
|
||||
# 同调装配运行态注入(迭代 11 第①批 T5——enc.attunement 携带;off=空集零扰动。淬毒 N 注入
|
||||
# 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install)
|
||||
attunement = (enc.get("attunement", []) as Array).duplicate()
|
||||
_attune_charges_ref = enc.get("attune_charges", {}) # 共享 dict 引用(level/adventure 同账本——use_aux 扣减直改)
|
||||
_attune_bank = 0
|
||||
_attune_safe_seen = 0
|
||||
_damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 0
|
||||
_reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0
|
||||
# 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现
|
||||
# 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后
|
||||
# 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节)
|
||||
var ap_bonus: int = int(enc.get("hero_ap_bonus", 0))
|
||||
if ap_bonus > 0:
|
||||
action_economy.queue_ap_delta(ap_bonus)
|
||||
# 工兵晨令(迭代 11 同调批 T5——前 effective_rounds 轮每轮 +1 AP;首轮在此入队,后续轮在轮始段续队)
|
||||
if _attune_has("dawn_order"):
|
||||
action_economy.queue_ap_delta(_attune_mag("dawn_order"))
|
||||
if not action_economy.notify_round_start():
|
||||
_fatal("行动经济 首轮 notify_round_start 被拒")
|
||||
return false
|
||||
@@ -305,6 +334,12 @@ func _settle() -> void:
|
||||
_emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "")
|
||||
continue
|
||||
_grant_routed(snap, "positive", "safe_cell", evt.cell, ri, "")
|
||||
# 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点)
|
||||
if _attune_has("dig_charge"):
|
||||
_attune_safe_seen += 1
|
||||
var cn_b: int = int(db.aux_item_row("dig_charge").get("charge_n", 999))
|
||||
if cn_b > 0 and _attune_safe_seen % cn_b == 0:
|
||||
_attune_bank += 1
|
||||
elif evt.kind == "mine":
|
||||
# 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
||||
# 标中不发(旗已随挖掘擦除、不入轮末判定基线)。
|
||||
@@ -360,30 +395,8 @@ func _settle() -> void:
|
||||
# 「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照(cell_type = 该格装板期摇定的类型身份),
|
||||
# polarity 由动作决定(开安全格/标中旗 = positive,开雷格/标错旗 = negative),
|
||||
# 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。
|
||||
# ---- 魔法道具(迭代 7 T2/T3——库存授予路由 + use_aux 四步受理编排;结构冻结见魔法道具 GDD)----
|
||||
|
||||
# 授予路由 aux 分支:正=入库存(发行「获得」行——逐行归因纪律);负=零兑付占位行。
|
||||
func _grant_aux(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int) -> bool:
|
||||
if polarity != "positive" or aux_inventory == null:
|
||||
# 负路由占位行=effect 型零兑付(雷安同池下标错旗/雷格摇中 aux 身份的负向字典行——逐行归因纪律:
|
||||
# 该格资源的负向兑付存在且为零;effect_id 空不入增益容器)
|
||||
_emit_row("effect", source_class, cell, ri, "道具随雷沉没(惩罚格不入库存)", 0,
|
||||
{"effect_id": "", "aux_sink": String(snap.get("effect_id", ""))}, "")
|
||||
return false
|
||||
var iid := String(snap.get("effect_id", ""))
|
||||
# 同调位持有上限(迭代 10 奖励重构批过渡承载——transitional_sync_slots,provisional 1):
|
||||
# 战斗内渠道遇满位沉没不入(战斗域零交互纪律——替换交互归停点渠道;正主 = 同调位谓词表,
|
||||
# 全渠道统一收口随魔法道具同调实施批)。逐行归因纪律:沉没行照发(「获得」的零兑付形态)。
|
||||
# 谓词单点 = AuxItems.can_accept(2026-10-02 修复批——四处手抄收口)。
|
||||
if not AuxItems.can_accept(1, aux_inventory.holding_count(), db):
|
||||
_emit_row("effect", source_class, cell, ri, "同调位已满,%s 沉没" % String(db.aux_item_row(iid).get("display_name", iid)), 0,
|
||||
{"effect_id": "", "aux_sink": iid, "sink_reason": "sync_slots_full"}, "")
|
||||
return false
|
||||
var arow: Dictionary = db.aux_item_row(iid)
|
||||
var rec: Dictionary = aux_inventory.add_item(iid, source_class, cell, ri)
|
||||
_emit_row("effect", source_class, cell, ri, "获得 %s" % String(arow.get("display_name", iid)), 1,
|
||||
{"aux_item": iid, "handle": int(rec.get("handle", 0))}, "")
|
||||
return true
|
||||
# ---- 魔法道具(use_aux 四步受理编排保留给同调主动件复用——迭代 11 第①批 T7:过渡授予路由
|
||||
# _grant_aux 随掉落/停点渠道退役拆除;能力件注入归 LevelRunner.set_attune_charges 骑库存面)----
|
||||
|
||||
|
||||
# use_aux 四步受理(registry use_orchestration 规范序——唯一调用权归本类;序即原子:
|
||||
@@ -402,6 +415,12 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary:
|
||||
func(c: Vector2i): return not board.is_revealed(c))
|
||||
if not bool(v.ok):
|
||||
return v # ② 拒绝零消耗
|
||||
# 掘进蓄能受理闸(迭代 11 同调批 T6——bank>0 才可发起,一行一点;积点不足拒绝零消耗)
|
||||
if String(v.item_id) == "dig_charge" and _attune_has("dig_charge"):
|
||||
if _attune_bank <= 0:
|
||||
var cn_info: int = int(db.aux_item_row("dig_charge").get("charge_n", 0))
|
||||
return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]}
|
||||
_attune_bank -= 1
|
||||
var kinds: Array = v.kinds
|
||||
if kinds.has("mine_reveal"):
|
||||
aux_inventory.apply_overlay("mine_reveal", v.resolved)
|
||||
@@ -410,7 +429,16 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary:
|
||||
if kinds.has("ap_grant"):
|
||||
if not action_economy.grant_ap(int(v.magnitude)):
|
||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||
if kinds.has("ap_charge_safe"):
|
||||
# 掘进蓄能兑付(迭代 11 同调批 T6——临时 AP 当轮生效,grant_ap 同 sapper_order 通道)
|
||||
if not action_economy.grant_ap(int(v.magnitude)):
|
||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||
var rec: Dictionary = aux_inventory.consume(handle)
|
||||
# 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用)
|
||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
var used_iid: String = String(rec.get("item_id", ""))
|
||||
if _attune_charges_ref.has(used_iid):
|
||||
_attune_charges_ref[used_iid] = maxi(0, int(_attune_charges_ref[used_iid]) - 1)
|
||||
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
|
||||
|
||||
|
||||
@@ -420,17 +448,26 @@ func set_aux_unlocked(ids: Dictionary) -> void:
|
||||
|
||||
|
||||
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
# 迭代 7 T3——aux 投放行授予路由(魔法道具 GDD:授予路由唯一调用方=战斗结算):正路由(挖开/标中)
|
||||
# → 道具入关卡域库存(来源可归因);负路由(雷格/标错旗摇中 aux 身份——雷安同池)→ 零兑付
|
||||
# 「道具随雷沉没」(雷格奖励沉没既有语义);清图批追加份同路由(清图翻倍语义对 aux 同样成立)。
|
||||
if String(snap.get("cell_type", "")) == RewardTable.AUX_ITEM_TYPE:
|
||||
return _grant_aux(snap, polarity, source_class, cell, ri)
|
||||
# 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 aux_item 保留但零投放:
|
||||
# 无 aux 行入池、BONUS_IDS 词表已除名);剩余类型走极性字典路由。
|
||||
var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||
if row.is_empty():
|
||||
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
||||
return false
|
||||
if polarity == "positive":
|
||||
row["power"] = int(snap.get("power", int(row.power))) # 区间行快照 power(身份批 roll 值)覆盖表值——授予期零随机口径(E14)
|
||||
# 惊巢反制(迭代 11 同调批 T5——底稿 §3:挖开雷格该次惩罚减半 + 反弹 N;duplicate 隔离——减半
|
||||
# 不回写极性字典;仅 source_class=mine_hit 受理,标错旗/负向字典行走原通道)
|
||||
if polarity == "negative" and source_class == "mine_hit" and _attune_has("nest_counter"):
|
||||
row = row.duplicate()
|
||||
var half: int = int(ceil(float(int(row.power)) / 2.0))
|
||||
_emit_row("effect", source_class, cell, ri, "惊巢反制——挖雷惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||
row.power = half
|
||||
var reflect: int = _attune_mag("nest_counter")
|
||||
if reflect > 0:
|
||||
var nct := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board",
|
||||
reflect, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {})
|
||||
_submit(nct, false)
|
||||
return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
|
||||
|
||||
@@ -537,6 +574,11 @@ func _judge_flag(evt: Dictionary, ri: int) -> void:
|
||||
return # 去重跳过(资格以雷格实例为键——排雷玩法 诚实重复上报的对侧);零明细行
|
||||
_flag_consumed[cell] = true
|
||||
_perfect_hit_true += 1
|
||||
# 标中殉爆(迭代 11 同调批 T5——底稿 §3:每标中一面旗,全体存活敌吃 N 群伤;无类型走 blast 通道)
|
||||
if _attune_has("flag_burst"):
|
||||
var fbt := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_flag_burst", "标中殉爆", "board",
|
||||
_attune_mag("flag_burst"), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {})
|
||||
_submit(fbt, false)
|
||||
var snap = _reward_economy.grant(cell)
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
@@ -747,6 +789,9 @@ func _exit_distribution() -> void:
|
||||
if not _install_new_board():
|
||||
_fatal("换图编排失败")
|
||||
return
|
||||
# 工兵晨令续队(迭代 11 同调批 T5——upcoming 轮号 = round_index+1 ≤ effective_rounds 才续,P4 甲表值化)
|
||||
if _attune_has("dawn_order") and round_index + 1 <= int(db.aux_item_row("dawn_order").get("effective_rounds", 0)):
|
||||
action_economy.queue_ap_delta(_attune_mag("dawn_order"))
|
||||
action_economy.notify_settlement_done(mode)
|
||||
action_economy.notify_round_start()
|
||||
_reset_round_facts()
|
||||
|
||||
+33
-2
@@ -25,6 +25,7 @@ const TABLE_MANIFEST := {
|
||||
"meta": "meta.tres", # 元进度实现批 2026-09-21(MetaTable 四行族——schema 与表同批落籍)
|
||||
"aux_items": "aux_items.tres", # 迭代 7 T1 魔法道具内容批(AuxItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注)
|
||||
"adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点)
|
||||
"attunement_growth": "attunement_growth.tres", # 迭代 11 第①批 同调实施批 T1(AttunementGrowthTable 单行全局——同调位里程碑谓词表;单行先例 Rule 4 豁免)
|
||||
}
|
||||
|
||||
var action_economy: ActionEconomyTable
|
||||
@@ -45,6 +46,7 @@ var shop: ShopTable = null
|
||||
var meta: MetaTable
|
||||
var aux_items: AuxItemTable # 迭代 7 T1 类型化持用(消费面走 aux_* 访问器)
|
||||
var adventure: AdventureTable
|
||||
var attunement_growth: AttunementGrowthTable # 迭代 11 第①批 T1 类型化持用(消费面走 attunement_* 访问器)
|
||||
|
||||
var is_ready := false
|
||||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||||
@@ -135,6 +137,7 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
||||
meta = loaded.get("meta")
|
||||
aux_items = loaded.get("aux_items")
|
||||
adventure = loaded.get("adventure")
|
||||
attunement_growth = loaded.get("attunement_growth")
|
||||
is_ready = not has_errors
|
||||
return not has_errors
|
||||
|
||||
@@ -211,14 +214,34 @@ func aux_item_row(item_id: String) -> Dictionary:
|
||||
return aux_items.row_by_id(item_id) if aux_items != null else {}
|
||||
|
||||
|
||||
# 活行词表(掉落投放 effect_id 白名单)
|
||||
# 过渡渠道活行(form 空 = 切片期过渡形态)——掉落投放/停点池/工坊解锁取数口径;同调行(form 非空)
|
||||
# 永不入过渡渠道(迭代 11 第①批 T1;T7 退役面切换后本口径随渠道消亡)
|
||||
func aux_transitional_rows() -> Array:
|
||||
return aux_items.transitional_rows() if aux_items != null else []
|
||||
|
||||
|
||||
# 同调行集(form 非空——装配覆盖层/效果接线的取数口径,迭代 11 第①批 T1/T4)
|
||||
func aux_attunement_rows() -> Array:
|
||||
return aux_items.attunement_rows() if aux_items != null else []
|
||||
|
||||
|
||||
# 活行词表(掉落投放 effect_id 白名单——过渡渠道口径)
|
||||
func aux_live_ids() -> Dictionary:
|
||||
var out := {}
|
||||
for row in aux_active_rows():
|
||||
for row in aux_transitional_rows():
|
||||
out[String(row.get("item_id", ""))] = true
|
||||
return out
|
||||
|
||||
|
||||
# ── 同调轴访问器(迭代 11 第①批 T1——消费面一律走访问器,禁直读行集)──
|
||||
func attunement_slot_cap() -> int:
|
||||
return attunement_growth.slot_cap if attunement_growth != null else 0
|
||||
|
||||
|
||||
func attunement_milestones() -> Array:
|
||||
return attunement_growth.milestones if attunement_growth != null else []
|
||||
|
||||
|
||||
# 行族四轴(数据契约,行集归 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}。
|
||||
@@ -296,6 +319,14 @@ func meta_conversion_row(attr: String) -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
# Y 刷新升级行单点读取(迭代 11 第①批 T4——键 = 等级,缺行返回空字典=未购/已满级合法态)
|
||||
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:
|
||||
|
||||
@@ -196,7 +196,12 @@ func _resolve_hero_attack(t: Dictionary) -> void:
|
||||
var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus)
|
||||
var crit: bool = bool(roll_read.crit)
|
||||
var discount: int = _roll_discount(int(roll_read.roll), threshold)
|
||||
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), _counter_delta(atk_type, armor))
|
||||
var cdelta: int = _counter_delta(atk_type, armor)
|
||||
# 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 ×
|
||||
# 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成)
|
||||
if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0:
|
||||
cdelta += attune_venom
|
||||
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), cdelta)
|
||||
var payload: Dictionary = {"target": String(target.display_name)}
|
||||
payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型)
|
||||
payload["roll"] = int(roll_read.roll)
|
||||
@@ -326,6 +331,9 @@ func _counter_delta(attack_type: String, armor: String) -> int:
|
||||
return int(row.get(armor, 0))
|
||||
|
||||
|
||||
# 淬毒刃纹 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动)
|
||||
var attune_venom: int = 0
|
||||
|
||||
# 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧)
|
||||
func clamped_pool(pool: String) -> int:
|
||||
var total := 0
|
||||
|
||||
@@ -64,6 +64,8 @@ var _shop_purchased := {} # product_id → 已购档数(现价
|
||||
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
|
||||
var aux_inventory: AuxItems = null # 魔法道具 关卡域库存(迭代 7 T3——AuxItems;跨战斗延续,
|
||||
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——拍板③甲;空集=零投放)
|
||||
var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——adventure params.attunement 下传,begin_battle enc 携带进战斗结算;空集=off 零扰动)
|
||||
var _attune_charges: Dictionary = {} # 同调主动件充能账本(迭代 11 第①批 T6——adventure 共享 dict 引用;settlement use_aux 扣减直改同引用)
|
||||
# 关卡末清空+快照计数入 level_finished 载荷)
|
||||
|
||||
var hero_atk_pct := 0 # 测试面板覆盖值(切片专用)
|
||||
@@ -464,6 +466,8 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_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(),
|
||||
"attunement": _attunement,
|
||||
"attune_charges": _attune_charges,
|
||||
"node_type": String(node.get("type", ""))}):
|
||||
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
|
||||
return false
|
||||
@@ -567,15 +571,32 @@ func set_aux_unlocked(ids: Dictionary) -> void:
|
||||
if _settlement != null:
|
||||
_settlement.set_aux_unlocked(ids)
|
||||
|
||||
|
||||
# 同调装配集注入(迭代 11 第①批 T5——冒险开始参数面;AdventureRunner 唯一调用方)
|
||||
func set_attunement(ids: Array) -> void:
|
||||
_attunement = ids.duplicate()
|
||||
|
||||
|
||||
# 同调主动件充能账本注入(迭代 11 第①批 T6——共享 dict 引用直改;能力件骑库存面注入:
|
||||
# 差额注入防续玩双份——resume 路径 restore_inventory 已含断点内 attune 记录)
|
||||
func set_attune_charges(charges: Dictionary) -> void:
|
||||
_attune_charges = charges
|
||||
if aux_inventory == null:
|
||||
aux_inventory = AuxItems.new(db)
|
||||
for iid in charges:
|
||||
var existing := 0
|
||||
for r in aux_inventory.snapshot_inventory():
|
||||
if String(r.item_id) == String(iid) and String(r.source_class) == "attunement" and not bool(r.used):
|
||||
existing += 1
|
||||
for i in range(maxi(0, int(charges[iid]) - existing)):
|
||||
aux_inventory.add_item(String(iid), "attunement", Vector2i(-1, -1), 0)
|
||||
|
||||
# 奖励节点兑付透传(迭代 7 T5——呈现层三选一 → 关卡域库存;来源归因 reward_node)。
|
||||
# 同调位持有上限(迭代 10 奖励重构批过渡承载):满位拒绝授予(奖励节点渠道过渡态 = 满位候选
|
||||
# 由呈现侧过滤不出;此处防御兜底)——替换交互归三选一渠道,全渠道统一收口随同调实施批。
|
||||
# 迭代 11 第①批 T7——同调位持有闸随过渡键退役拆除(aux 卡出池后仅剩防御面;能力件容量闸
|
||||
# 正主 = 营地装配面 Attunement.slots 满位禁选)。
|
||||
func grant_aux_item(item_id: String, source: String) -> Dictionary:
|
||||
if aux_inventory == null:
|
||||
aux_inventory = AuxItems.new(db)
|
||||
if not AuxItems.can_accept(1, aux_inventory.holding_count(), db): # 同调位守卫单点(2026-10-02)
|
||||
push_warning("[关卡节点地图] 同调位已满,授予拒绝: %s(%s)" % [item_id, source])
|
||||
return {}
|
||||
return aux_inventory.add_item(item_id, source, Vector2i(-1, -1), 0)
|
||||
|
||||
|
||||
|
||||
@@ -150,3 +150,65 @@ func hero_override() -> Dictionary:
|
||||
"max_hp": forge_flat("max_hp"),
|
||||
"armor_class": forge_flat("armor_class"),
|
||||
}
|
||||
|
||||
|
||||
# ---- 同调位账本(迭代 11 第①批 T4——装配关系/同调位求值/Y 刷新升级;设计权威 proposal §2 + 任务书 P1 甲)----
|
||||
|
||||
# 装配切换(营地装配覆盖层唯一写口):已装 → 卸下恒可;未装 → 非同调行拒绝 + 满位禁选;
|
||||
# 装配关系写存档持久(冒险中途不可换装——底稿 §8.5,本口只服务营地)。
|
||||
func toggle_attunement(item_id: String) -> bool:
|
||||
var equipped: Array = save.attunement_equipped()
|
||||
if not equipped.has(item_id):
|
||||
var arow: Dictionary = db.aux_item_row(item_id)
|
||||
if arow.is_empty() or String(arow.get("form", "")) == "":
|
||||
return false # 非同调行(过渡/退役行)禁装配
|
||||
if equipped.size() >= Attunement.slots(db, save):
|
||||
return false # 满位禁选
|
||||
equipped.append(item_id)
|
||||
else:
|
||||
equipped.erase(item_id)
|
||||
save.set_attunement_equipped(equipped)
|
||||
return save.write_to_disk()
|
||||
|
||||
|
||||
# 营地装配面投影(账面归本类、拼句归 CampOverlay——_camp_data 同款分工)
|
||||
func attune_camp_status() -> Dictionary:
|
||||
var equipped: Array = save.attunement_equipped()
|
||||
var items: Array[Dictionary] = []
|
||||
for row in db.aux_attunement_rows():
|
||||
var iid := String(row.get("item_id", ""))
|
||||
items.append({
|
||||
"item_id": iid,
|
||||
"display_name": String(row.get("display_name", "")),
|
||||
"form": String(row.get("form", "")),
|
||||
"equipped": equipped.has(iid),
|
||||
})
|
||||
var lvl := save.attune_refresh_level()
|
||||
var next_row: Dictionary = db.meta_attune_refresh_row(lvl + 1)
|
||||
return {
|
||||
"slots": Attunement.slots(db, save),
|
||||
"equipped": equipped,
|
||||
"items": items,
|
||||
"refresh": {
|
||||
"level": lvl,
|
||||
"charges": attune_refresh_charges(),
|
||||
"price": int(next_row.get("keystone_cost", 0)),
|
||||
"maxed": next_row.is_empty(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# 每冒险开局刷新次数 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))
|
||||
|
||||
|
||||
# Y 升级购买(直购价 = 下一级行表值 keystone_cost;已满级/余额不足 false 零副作用——buy_forge 同款)
|
||||
func buy_attune_refresh() -> bool:
|
||||
var next_row: Dictionary = db.meta_attune_refresh_row(save.attune_refresh_level() + 1)
|
||||
if next_row.is_empty():
|
||||
return false
|
||||
if not save.spend_keystone(int(next_row.get("keystone_cost", 0))):
|
||||
return false
|
||||
save.set_attune_refresh_level(save.attune_refresh_level() + 1)
|
||||
return save.write_to_disk()
|
||||
@@ -59,7 +59,7 @@ func roll_board(eligible: Array) -> bool:
|
||||
_clear_granted = false
|
||||
_clear_snapshot = {}
|
||||
var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号)
|
||||
var cell_rows: Array = _rollable_rows() # cell 池正向行 weight>0(类型频次)
|
||||
var cell_rows: Array = _biased_cell_rows() # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰)
|
||||
var clear_rows: Array = _pool_rows("clear_default")
|
||||
# ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布)
|
||||
var draft := {}
|
||||
@@ -174,6 +174,27 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary:
|
||||
|
||||
# ---- F1 选行算子纯函数 ----
|
||||
|
||||
# 偏运硬币 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动)
|
||||
var luck_bias: int = 0
|
||||
# 稀有正向资源 kinds(底稿 §3——权重投影面,池结构零触碰)
|
||||
const LUCK_BIAS_KINDS := ["heal", "gp", "pierce", "magic"]
|
||||
|
||||
|
||||
# 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动)
|
||||
func _biased_cell_rows() -> Array:
|
||||
if luck_bias <= 0:
|
||||
return _rollable_rows()
|
||||
var out: Array = []
|
||||
for row in _rollable_rows():
|
||||
if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))):
|
||||
var biased: Dictionary = row.duplicate()
|
||||
biased["weight"] = int(row.weight) + luck_bias
|
||||
out.append(biased)
|
||||
else:
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
func weighted_pick(rows: Array, r: int) -> String:
|
||||
var total := _pool_weight(rows)
|
||||
assert(r >= 0 and r < total, "weighted_pick 取号越域(纯函数域守卫)")
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
# 域清单与 owner 分工(内容 owner 定口径,本类照单承载、不替任何人定义进度):
|
||||
# keystone_balance / forge_levels / item_unlocks / level_clear_counts / boss_first_kill
|
||||
# —— 内容 owner = 元进度.md(营地三柜台:钥石余额/属性锻造/道具工坊/通关次数/首杀标记)
|
||||
# boss_kill_count / attunement_equipped / attune_refresh_level
|
||||
# —— 内容 owner = 魔法道具.md(同调位账本,M-4 迭代 11 第①批 T2):Boss 击杀总数〔含重复〕=
|
||||
# attunement_growth 谓词表计数源;装配关系 = 局外进度事实(元进度域同族——非契约 2 反面清单的
|
||||
# 「局内道具库存」,后者=冒险进行中瞬态);attune_refresh_level = Y 刷新升级购买等级
|
||||
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
||||
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
||||
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
||||
# breakpoint(冒险断点快照——停点粒度续玩域)—— 内容 owner = 冒险结构.md(七字段拍板面
|
||||
# +恢复必需附随件:主种子/取数计数器/钥石累计/已到节点序/道具库存);契约 2 域界反面清单
|
||||
@@ -30,8 +36,7 @@
|
||||
class_name SaveManager
|
||||
extends RefCounted
|
||||
|
||||
const SAVE_FORMAT_VERSION := 3 # registry schema_version:v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构;
|
||||
# 旧档缺键 = 合法空态);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
||||
const SAVE_FORMAT_VERSION := 4 # registry schema_version:v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
||||
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||
|
||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
||||
@@ -48,6 +53,7 @@ func _init(path: String = "user://save.json") -> void:
|
||||
# 生产迁移链自注册(v2 起——测试钩 register_migration 仍可叠挂更早版本步骤)
|
||||
register_migration(1, _migrate_v1_v2)
|
||||
register_migration(2, _migrate_v2_v3)
|
||||
register_migration(3, _migrate_v3_v4)
|
||||
|
||||
|
||||
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
||||
@@ -66,6 +72,14 @@ func _migrate_v2_v3(data: Dictionary) -> Dictionary:
|
||||
return data
|
||||
|
||||
|
||||
# v3→v4:同调批扩域(迭代 11 第①批 T2——meta 三新域 boss_kill_count/attunement_equipped/
|
||||
# attune_refresh_level,旧档缺键由 _normalize 按默认补齐,无结构迁移体;版本号推进仅为
|
||||
# schema_version 对齐——v2→v3 先例)
|
||||
func _migrate_v3_v4(data: Dictionary) -> Dictionary:
|
||||
data["version"] = SAVE_FORMAT_VERSION
|
||||
return data
|
||||
|
||||
|
||||
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
||||
|
||||
func _default_data() -> Dictionary[String, Variant]:
|
||||
@@ -77,6 +91,9 @@ func _default_data() -> Dictionary[String, Variant]:
|
||||
"item_unlocks": {},
|
||||
"level_clear_counts": {"1": 0, "2": 0, "3": 0},
|
||||
"boss_first_kill": {},
|
||||
"boss_kill_count": 0,
|
||||
"attunement_equipped": [],
|
||||
"attune_refresh_level": 0,
|
||||
"recent_summaries": [],
|
||||
"first_adventure_done": false,
|
||||
},
|
||||
@@ -207,14 +224,55 @@ func boss_first_kill_marked(level_index: int) -> bool:
|
||||
return _data["meta"]["boss_first_kill"].has(str(level_index))
|
||||
|
||||
|
||||
# ---- 同调位账本域(迭代 11 第①批 T2——里程碑谓词表计数源 + 装配关系 + 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))
|
||||
|
||||
|
||||
# 装配关系(同调目录 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 = []
|
||||
for v in item_ids:
|
||||
out.append(String(v))
|
||||
_data["meta"]["attunement_equipped"] = out
|
||||
|
||||
|
||||
# Y 刷新升级购买等级(0 = 未购 → 每冒险刷新次数 0;等级行值归营地升级行族——T4 接线)
|
||||
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 attunement_counters() -> Dictionary:
|
||||
var total := 0
|
||||
var counts: Dictionary = _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) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
|
||||
|
||||
|
||||
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
|
||||
# 钥石入账(summary.keystone_total——迭代 10 起逐杀掉落已在冒险内计入,boss_drops 参数降级为
|
||||
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + 摘要头部插入
|
||||
# (cap 10,最新在前,元素附 keystone_drops 副本——复盘可归因)。
|
||||
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
||||
# (迭代 11 第①批 T2——同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
|
||||
# keystone_drops 副本——复盘可归因)。
|
||||
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
||||
add_keystone(int(summary.get("keystone_total", 0)))
|
||||
var cleared := int(summary.get("levels_cleared", 0))
|
||||
@@ -222,6 +280,7 @@ func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> voi
|
||||
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 # 迭代 11 T2——Boss 恒唯一终点:levels_cleared 即本次冒险 Boss 击杀数(含重复)
|
||||
var entry: Dictionary = summary.duplicate(true)
|
||||
entry["boss_drops"] = boss_drops.duplicate(true)
|
||||
var arr: Array = _data["meta"]["recent_summaries"]
|
||||
|
||||
Reference in new issue
Block a user