接手前任中断的 T3 半成品(脏文件三件:battle_orchestrator/effects engine/feats.tres),方向核实无误后续做收口; 承接修正四处随本提交留痕:①perfect_defense 的 when 用了 fact 封闭 9 项外的 flags_hit_all(装载校验必红)—— 改 flags_count gte 1 保守判定,全中闸=flag_settle 发射点施加面承载(载荷字段仅供观测);②has 算子语义与行 不匹配(数组包含对 String 恒假)——落非空在场判定(value 缺省)+数组包含(value 给定),查询面落法=代拍板 白名单;③前任漏写 _harmful_adjacent 助手(flag_hit 发射点调用必炸);④counter_add set 键语义 bool() 吞 0 (清零行 set:0 被当 false 走加零通道)——改 has(set) 置值模式,四处施加点同修。 本提交主干(专长族全量迁移——任务书 §六 T3): - damage_controller:feat_flags 旗面整体退役,五事件施加面接线——attack_commit 命中随单据 context 携带 (发射点=开单入口,任务书 §3.1 定稿位;主角攻击单恒段内首单,开单现场=结账现场),rider/改写/加值/ 段数/状态五族按迁移前算序施加(重锤两拍/专注/spike 恒全额/猎弱移档/魔网湛蓝猎手克制直调/整备每层 +1/厚积满层倍乘/奥术+猎巢+执拗 rider 合计后档位暴击);armor_roll 免掷恒折扣、clamp_check 双面 (hero+board_typed)delta 窗口、kill 溢出弹射(landed×value%)、damage_taken shield 加法族合计。 _feat_clamp_bonus/13 处旗消费点拆除。 - battle_orchestrator:_combat_segment 换代(dig_streak/charge_layers 注伤害控制器运行态;连珠矢段数 施加位紧随主角攻击单开立=迁移前开单位,flush 兑现序逐位一致);round_start 发射位前移至行动经济轮 切换前(树规则 grant_ap queue 当轮入队当轮兑现——先手压制首轮语义锚);深挖快照滚动拆除(计数器 reset_at 声明承载);熔火/连击旧计数变量退役。 - action_economy:feat_ap_carry/feat_dig_extra/deep_paid_count 旧注入旗拆除;dig_paid 施加面落地 (counter_spend 对价:AP 富余付全价积计数、不足 fallback 只付基础 1——2026-10-04 降级口径逐位保留)。 - feat_table schema:HOOKS 23 挂点枚举/MINE_HOOKS/EFFECT_KEYS/hook+effects 双形态校验全删——triggers 订阅形态唯一合法行形(行键白名单收口 feat_id/display_name/rarity/archetype/triggers/desc/note)。 - feats_engine:hooks_for 挂点投影退役(订阅装载序=feats 表序,由效果引擎 rebuild 承载)。 - battle_narrator:专长卡片文案表值化(summaries 硬编码映射拆除,读行 desc——27 件措辞逐字迁移)。 - smoke 断言面随迁(任务书 v1.1 补列项):构造表 5 行换代 triggers 形态;挂点投影断言换代订阅装载面; ④-⑧ 求值断言(_feat_flags_for 直呼+旗读数)全量改写为 rebuild+dispatch 命中面(27 件逐事件覆盖); 深挖钻头付费开掘回归闸改 counter_spend 计数器面;特性卡 desc 直读断言新增。 映射表对拍注记(行为等价优先,.tres 尾注同步留痕):猎巢人/矿工的执拗映射表标 modify_ticket/attack_bonus, 现行为必中附加通道(hero_rider 后置于乘区与下限)——落 add_ticket_rider(per 取数载荷);整备军备现行为 双效果(轮末转蓄力+每层首段攻 +1)——两行订阅;深挖钻头 per-round 计数器对(paid/given)复刻旧 v2 跨轮不对称语义(given 不随轮重置)。 验证:冒烟 800 项 0 失败(三闸过)· 单元 17 套全绿 · S1 魔法流对拍预检=MC formed/off 两态与 T0 pre 锚 (83000 种子段)同种子逐位一致(2×5 局,三档只测不判)——14 件迁移行实战斗承载零分岔。 设计权威:design/research/研究-内容数据驱动扩展架构-2026-10-06.md(案乙)。 任务书:design/research/任务书-内容数据驱动扩展批-2026-10-06.md §六 T3、§五映射表、§七契约 2/4。
260 lines
11 KiB
GDScript
260 lines
11 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
|
||
# Date: 2026-09-10
|
||
# 行动经济:被动账本(AP 硬重置 / 统一价目 / 三路径结束仲裁 cleared > stopped > ap_exhausted /
|
||
# RAP 池战斗域 / 双发射源之 stopped 直发)。相位域 = {Idle, Active, Waiting}。
|
||
# 实现 GDD:design/archive/gdd-2026-09-14/a3-action-economy.md(系统 ID A3·行动经济——registry 追溯锚,命名规范见 CLAUDE.md)
|
||
class_name ActionEconomy
|
||
extends RefCounted
|
||
|
||
signal round_end_stopped(payload: Dictionary) # 双发射源之停手路径(行动经济 直发;payload 与 排雷玩法 棋盘路径同构)
|
||
|
||
const PHASE_IDLE := "Idle"
|
||
const PHASE_ACTIVE := "Active"
|
||
const PHASE_WAITING := "Waiting"
|
||
|
||
# 相位轴(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: int = Phase.IDLE
|
||
var phase: String:
|
||
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[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
|
||
var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图)
|
||
var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零)
|
||
# 蓄力流运行态(迭代 12 T5 → 内容数据驱动扩展批 T3 换代:旧 feat_ap_carry/feat_dig_extra 注入旗
|
||
# 退役——转蓄力=round_end grant_ap(mode:transfer) 订阅(finish_round_stop 定夺)、深挖对价=dig_paid
|
||
# counter_spend 订阅(request_dig 施加面);charge_layers=蓄力层账本)
|
||
var _charge_layers := 0
|
||
var charge_layers: int:
|
||
get: return _charge_layers
|
||
# 效果引擎(内容数据驱动扩展批 T1——dig_paid/charge_full 发射面;round_end 转存决策面)
|
||
var _fx: EffectEngine = null
|
||
var _last_stop_ap := 0 # 停手侧剩余 AP 记账(转存决策移至 round_end 发射点——grant_ap(transfer) 订阅定夺)
|
||
|
||
|
||
func set_fx(fx: EffectEngine) -> void:
|
||
_fx = fx
|
||
|
||
|
||
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:
|
||
"dig":
|
||
return ae.dig_ap_cost # 基础挖格成本恒表值(钻头对价挂付费开掘侧——2026-10-04 缺陷修复)
|
||
"flag_place":
|
||
return ae.flag_place_ap_cost
|
||
"move":
|
||
return ae.move_ap_cost
|
||
"chain":
|
||
return ae.chain_ap_cost
|
||
assert(false, "未知动作价目: " + kind)
|
||
return 0
|
||
|
||
|
||
# 通用扣减(排雷玩法 用于旗动作;挖握走 request_dig——cleared 仲裁需要 clears_map 参数)
|
||
func try_spend(action_kind: String) -> bool:
|
||
if _phase != Phase.ACTIVE:
|
||
_reject("phase")
|
||
return false
|
||
return _spend_and_arbitrate(action_kind, false)
|
||
|
||
|
||
# 排雷玩法 挖掘专用:chain_size = 连锁附加格数(不含首格);clears_map = 本次动作后全图安全格开完。
|
||
# 深挖钻头付费开掘(内容数据驱动扩展批 T3——dig_paid counter_spend 订阅施加面,2026-10-04 缺陷
|
||
# 修复口径不变):按序消耗族——AP 富余(≥基础+对价)时本动作付全价并积本轮付费计数(效果引擎
|
||
# 计数器=授予段追加产出配额取数面);AP 不足全价时 fallback 降级只付基础 1 照常开格、不积计数
|
||
# ——持有钻头永不锁死普通挖格(散件保底 B-2)。
|
||
func request_dig(chain_size: int, clears_map: bool) -> bool:
|
||
if _phase != Phase.ACTIVE:
|
||
_reject("phase")
|
||
return false
|
||
var cost := _cost("dig")
|
||
# dig_paid 发射+施加(内容数据驱动扩展批 T3——付费开掘发起、动作经济闸收对价前:
|
||
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价)
|
||
if _fx != null:
|
||
for h in _fx.dispatch("dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
|
||
if String(h.get("kind", "")) != "counter_spend":
|
||
continue
|
||
var extra := int(h.get("value", 1))
|
||
if _ap >= cost + extra:
|
||
cost += extra
|
||
_fx.counter_add(String(h.get("counter_id", "")), 1)
|
||
if _ap < cost:
|
||
_reject("resource_face")
|
||
return false
|
||
return _spend_cost_and_arbitrate(cost, clears_map)
|
||
|
||
|
||
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
|
||
if _ap < cost:
|
||
_reject("resource_face")
|
||
return false
|
||
_ap -= cost
|
||
if clears_map:
|
||
# cleared 胜出(同帧仲裁 cleared > ap_exhausted);剩余 AP 当场清零作废、不进 RAP(EC7/EC10)
|
||
_ap = 0
|
||
_finish_round("cleared", "new_board")
|
||
elif _ap == 0:
|
||
# AP 归 0 瞬间轮次结束——不存在 0-AP 操作窗口
|
||
_finish_round("ap_exhausted", "same_board")
|
||
return true
|
||
|
||
|
||
func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool:
|
||
var cost := _cost(action_kind)
|
||
if _ap < cost:
|
||
_reject("resource_face")
|
||
return false
|
||
_ap -= cost
|
||
if clears_map:
|
||
# cleared 胜出(同帧仲裁 cleared > ap_exhausted);剩余 AP 当场清零作废、不进 RAP(EC7/EC10)
|
||
_ap = 0
|
||
_finish_round("cleared", "new_board")
|
||
elif _ap == 0:
|
||
# AP 归 0 瞬间轮次结束——不存在 0-AP 操作窗口
|
||
_finish_round("ap_exhausted", "same_board")
|
||
return true
|
||
|
||
|
||
# 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。
|
||
# 整备军备改写(迭代 12 T5——feat_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存:
|
||
# 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义)。
|
||
# 内容数据驱动扩展批 T1:本方法改记账制——剩余 AP 存 _last_stop_ap 后清零,转存决策移至
|
||
# round_end 发射点 finish_round_stop(grant_ap(mode:transfer) 订阅定夺;双轨期旧旗 feat_ap_carry
|
||
# 并行生效,行为与迁移前逐位一致)。
|
||
func request_stop() -> bool:
|
||
if _phase != Phase.ACTIVE:
|
||
_reject("phase")
|
||
return false
|
||
_last_stop_ap = _ap
|
||
_ap = 0
|
||
_finish_round("stopped", "same_board")
|
||
round_end_stopped.emit({"path_tag": "stopped", "continuation_hint": "same_board"})
|
||
return true
|
||
|
||
|
||
# 轮末停手转存决策(round_end 发射点调用——编排器授予段尾):transfer=true=剩余 AP 转蓄力层
|
||
# (上限 3,整备军备面);false=1:1 转存 RAP(缺省)。非停手轮(cleared/耗尽)_last_stop_ap=0
|
||
# 两分支均零操作。转蓄后达上限的沿发 charge_full(R6 框架先行——现无订阅者)。
|
||
func finish_round_stop(transfer: bool) -> void:
|
||
if transfer:
|
||
var prev_layers := _charge_layers
|
||
_charge_layers = mini(3, _charge_layers + _last_stop_ap)
|
||
if _fx != null and _charge_layers >= 3 and prev_layers < 3:
|
||
_fx.dispatch("charge_full", {"layers": _charge_layers})
|
||
else:
|
||
_rap += _last_stop_ap
|
||
_last_stop_ap = 0
|
||
|
||
|
||
# 轮内刷新(2026-09-12 方案 A 用户拍板;等待期通道 2026-09-15 用户拍板整体移除):唯一入口 =
|
||
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
|
||
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
|
||
func request_refresh() -> bool:
|
||
if _phase != Phase.ACTIVE:
|
||
_reject("phase")
|
||
return false
|
||
if _rap < db.action_economy.rap_refresh_cost:
|
||
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
|
||
_rap -= db.action_economy.rap_refresh_cost
|
||
return true
|
||
|
||
|
||
# 战斗结算 回发:结算完成(仅记录编排意图;phase 停留 Waiting——AC-14 门不被绕过)
|
||
func notify_settlement_done(next_round_mode: String) -> bool:
|
||
if _phase != Phase.WAITING:
|
||
_reject("phase")
|
||
return false
|
||
_settlement_consumed = true
|
||
_next_round_mode = next_round_mode
|
||
return true
|
||
|
||
|
||
# 下轮行动点修正排队(格面资源八型批 2026-09-20):action 型正负行结算期调用,同轮多次授予累加;
|
||
# 相位无关(授予发生在结算段,此时本类可能处于 ACTIVE/WAITING 任一相位)
|
||
# 第 9 API(registry 行动经济×① 接口漂移预授权——迭代 7 T2 秘宝内容批落位):ap_grant 道具的
|
||
# 当轮行动点加量(GDD「加行动点效果给当轮」——区别于 queue_ap_delta 的下轮排队语义)。Active 受理、
|
||
# 无上限域、零随机;使用道具本身零扣费(成本在获取时已付——捡道具格不出战斗奖励)。
|
||
func grant_ap(amount: int) -> bool:
|
||
if amount <= 0 or _phase != Phase.ACTIVE:
|
||
_reject("grant_ap")
|
||
return false
|
||
_ap += amount
|
||
return true
|
||
|
||
|
||
func queue_ap_delta(delta: int) -> void:
|
||
_pending_ap_delta += delta
|
||
|
||
|
||
func pending_ap_delta() -> int:
|
||
return _pending_ap_delta
|
||
|
||
|
||
func notify_round_start() -> bool:
|
||
if _phase == Phase.IDLE:
|
||
pass # 战斗首轮唯一出口
|
||
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
|
||
_settlement_consumed = false
|
||
return true
|
||
|
||
|
||
func notify_combat_end() -> bool:
|
||
if _phase != Phase.WAITING:
|
||
_reject("phase")
|
||
return false
|
||
_phase = Phase.IDLE
|
||
_rap = 0 # RAP 战斗域清零(用户拍板;HUD框架可见反馈义务)
|
||
_ap = 0
|
||
return true
|
||
|
||
|
||
func _finish_round(path_tag: String, continuation_hint: String) -> void:
|
||
# 实现层断言兜底(行动经济 轮次结束仲裁表):耗尽轮剩余恒为 0;清图轮剩余已当场清零(不进储备池)。
|
||
# 两断言守在唯一仲裁出口,未来若在扣费与仲裁之间挪动 AP 写入在此红灯。
|
||
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
|
||
|
||
|
||
func _reject(source: String) -> void:
|
||
if source == "phase":
|
||
push_warning("[行动经济] 动作被拒(%s 相位守卫/资源)" % source)
|
||
else:
|
||
push_warning("[行动经济] 资源不足拒绝")
|