P1-6 类型闸(须 T1-T4 全落后——本波按执行顺序锁殿后): - tools/audit_type_hints.py 新机器闸(V7): R1 无类型容器注解/R2 有值返回缺返回类型/ R3 参数缺类型三规则+行级 # type-hint: ignore 显式白名单通道;产品码 core/data/presentation - 存量收口: 368 处显式 Array/Dictionary 非参数化注解→机械参数化(Array[Variant]/ Dictionary[Variant, Variant] 零行为面);13 处参数真类型(SaveManagerCore/ConfigDb/ BattleOrchestrator/MinesweeperRules/LevelRunner/SettingsStore 等) - 白名单豁免四处: board_generator generate/_attempt/legality/_finalize 的 db 参—— 测量面 _BoardRowStub 冒烟夹具鸭子型注入(typed 参拒 stub) - project.godot 拉起 gdscript/warnings/untyped_declaration=1(V7 源码断言在 smoke _smoke_type_gate) - static_scan 新 S9 类型闸规则;顺修历史 cd 错位(9-24 布局迁移后 /.. 落 game/ 致守卫 恒跳过静默空转——修复后 S1-S4/S9 首次实跑全绿)+S1/S2 增 ADR-0007-exempt 标记豁免通道 (main _seed_value 引擎熵源=文档化豁免面) 验收: 冒烟 1081/0(+V7×2)+run_all 27 套+static_scan 全绿+probe click+render golden+对拍三变体全同
134 lines
5.3 KiB
GDScript
134 lines
5.3 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
|
||
# Date: 2026-09-10
|
||
# 增益状态·统一状态容器:三向效果同构实例 · 单一时限量纲 remaining_settlements(先兑付后衰减)·
|
||
# 按行叠层(stack 授予不触碰 R)· 授予时数值快照抗热重载 · 零随机零信号。
|
||
# 注:MVP 单敌假设经 敌人系统 回验不成立(定多敌)——enemy 侧 side-wide 语义按 registry 改写执行。
|
||
# 实现 GDD:design/gdd/systems/战斗循环/增益状态.md(系统 ID B3·增益状态——registry 追溯锚,命名规范见 CLAUDE.md)
|
||
class_name StatusContainer
|
||
extends RefCounted
|
||
|
||
var db: ConfigDb
|
||
var _open := false
|
||
var _instances: Array[Dictionary] = [] # 插入序;元素 = {effect_id, layers, remaining, source_class, origin_cell, round_index, snap_m, snap_unit, snap_display}
|
||
|
||
|
||
func _init(db_ref: ConfigDb) -> void:
|
||
db = db_ref
|
||
|
||
|
||
func notify_battle_start() -> bool:
|
||
if _open:
|
||
push_warning("[增益状态] Active 下重复 notify_battle_start(EC11)")
|
||
return false
|
||
_open = true
|
||
_instances.clear()
|
||
return true
|
||
|
||
|
||
# 唯一授予入口(挖掘期零直写——战斗结算 结算编排期统一调用)。
|
||
# duration_override(敌人技能池批——增益技施放):技能行 buff_duration 覆盖状态行时长(-1 = 状态表值)。
|
||
# 施放是持续量的 owner(proposal §2.2 字段词汇表"buff_duration 增益持续回合数");其他授予路径传缺省。
|
||
func apply_effect(effect_id: String, source: Dictionary, duration_override: int = -1) -> bool:
|
||
if not _open:
|
||
push_warning("[增益状态] 域闭授予(EC1)")
|
||
return false
|
||
var row: Dictionary[Variant, Variant] = db.status_effect_row(effect_id)
|
||
if row.is_empty():
|
||
push_warning("[增益状态] 未知 effect_id(EC2): %s" % effect_id)
|
||
return false
|
||
var policy: String = row.stack_policy
|
||
var dur: int = int(row.duration_settlements) if duration_override < 1 else duration_override
|
||
var max_stack: int = int(row.max_stack)
|
||
# 授予时快照(抗表重载——EC9)
|
||
var snap_m: int = int(row.magnitude)
|
||
if source.has("magnitude_override"):
|
||
snap_m = int(source["magnitude_override"]) # 旗溢价贯穿(迭代14② T4——标中旗 percent 形态同乘快照;缺省路径零扰动)
|
||
var snap_unit: String = String(row.unit)
|
||
var snap_display: String = String(row.display_name)
|
||
match policy:
|
||
"independent":
|
||
_instances.append(_new_instance(effect_id, 1, dur, source, snap_m, snap_unit, snap_display))
|
||
return true
|
||
"refresh":
|
||
for inst in _instances:
|
||
if inst.effect_id == effect_id:
|
||
inst.remaining = dur # R ← D(取实例快照 D——同表值);L 不变;空刷新恒 true
|
||
return true
|
||
_instances.append(_new_instance(effect_id, 1, dur, source, snap_m, snap_unit, snap_display))
|
||
return true
|
||
"stack":
|
||
for inst in _instances:
|
||
if inst.effect_id == effect_id:
|
||
if inst.layers >= max_stack:
|
||
return false # 满层拒绝原子——容器零写入(拒收由战斗结算记"奖励未生效"行)
|
||
inst.layers += 1 # stack 授予不触碰 R(spec 决议)
|
||
return true
|
||
_instances.append(_new_instance(effect_id, 1, dur, source, snap_m, snap_unit, snap_display))
|
||
return true
|
||
push_warning("[增益状态] 未知 stack_policy")
|
||
return false
|
||
|
||
|
||
func _new_instance(effect_id: String, layers: int, dur: int, source: Dictionary, m: int, unit: String, display: String) -> Dictionary:
|
||
return {
|
||
"effect_id": effect_id,
|
||
"layers": layers,
|
||
"remaining": dur,
|
||
"source_class": String(source.get("source_class", "")),
|
||
"origin_cell": source.get("origin_cell", Vector2i(-1, -1)),
|
||
"round_index": int(source.get("round_index", 0)),
|
||
"snap_m": m,
|
||
"snap_unit": unit,
|
||
"snap_display": display,
|
||
}
|
||
|
||
|
||
# 只读投影(插入序稳定枚举;六归因字段 + display_name + 授予时快照)
|
||
func query_active(side: String) -> Array:
|
||
var out: Array[Dictionary] = []
|
||
for inst in _instances:
|
||
var row: Dictionary[Variant, Variant] = db.status_effect_row(inst.effect_id)
|
||
if row.is_empty():
|
||
continue
|
||
if String(row.target_side) != side:
|
||
continue
|
||
out.append({
|
||
"effect_id": inst.effect_id,
|
||
"layers": inst.layers,
|
||
"remaining_settlements": inst.remaining,
|
||
"source_class": inst.source_class,
|
||
"origin_cell": inst.origin_cell,
|
||
"round_index": inst.round_index,
|
||
"display_name": inst.snap_display,
|
||
"snap_magnitude": inst.snap_m,
|
||
"snap_unit": inst.snap_unit,
|
||
"effective_value": inst.layers * inst.snap_m, # L×m 加算(percent 越域不 clamp——收口归 战斗结算)
|
||
})
|
||
return out
|
||
|
||
|
||
# 轮末衰减:先兑付后衰减;每实例恰 −1;归 0 移除;返回移除数 k(恰一次执行由 战斗结算 编排信任承载)
|
||
func decay_round() -> int:
|
||
if not _open:
|
||
return 0 # EC13 域闭兜底读法——容器恒空,无独立分支不诊断
|
||
var k := 0
|
||
var survivors: Array[Dictionary] = []
|
||
for inst in _instances:
|
||
inst.remaining -= 1
|
||
if inst.remaining <= 0:
|
||
k += 1
|
||
else:
|
||
survivors.append(inst)
|
||
_instances = survivors
|
||
return k
|
||
|
||
|
||
func notify_battle_end() -> bool:
|
||
if not _open:
|
||
push_warning("[增益状态] BattleClosed 下重复 notify_battle_end(EC11)")
|
||
return false
|
||
_open = false
|
||
_instances.clear() # 全清不跨战斗
|
||
return true
|