T1 数据面:AuxItemTable schema(七字段+retired/deferred 两 additive 标记+真视窄域/board_wide 双承重护栏)+六行 tres(三有效+罗盘 retired 留痕+显影尘/全图卷 deferred)+ConfigDB 加载/访问器+RewardTable 十型扩值(aux_item)+三投放行 5/4/1+aux_none 负占位行(雷格极性字典兜底零兑付)+双表悬空校验(reward_dangling_aux,负占位豁免)+F2 投放维度覆盖(豁免面 plan/info/counter 登记)+两 fatal 夹具(expect=1 复制真表注入违规)+run_all manifest+2 T2 core 骨架:AuxItems 类(api 8 入口+probe 探针;零随机零特判;Used 不可逆;真视恒可用/预读点击级零信息拒绝;3x3 边缘窗口∩板)+action_economy.grant_ap 第 9 API(当轮加量,区别 queue_ap_delta 排队) T3 授予路由+生命周期:_grant_routed aux 分支(正=入库存发「获得」行;负=零兑付 effect 型「道具随雷沉没」行)+use_aux 四步受理(相位 gate→validate→逐 kind 空间先 AP 后→consume;结算明细无道具行)+叠加层三清(装板点/战斗结束)+LevelRunner 关卡域库存持有注入+关卡末 reset+aux_cleared 快照计数扩签 冒烟锚随词表扩值更新:九型→十型封闭/字典 18→22 行;负向字典扫描三处稳健化(aux 不入五型轴+break 改 percent_map 三型齐备+rng 每板新实例——原第一板 break 掩盖 set_master_seed _counter 守卫违例) 闸:冒烟 577/0(净增 1)+单测 11 套全过(含两新夹具)+四探针 PASS
193 lines
7.9 KiB
GDScript
193 lines
7.9 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# Validation Question: 道具从掉落到使用的全程零随机零特判——同操作序列重放两遍,库存与叠加层投影逐位相同(P0)
|
||
# Date: 2026-09-29(迭代 7 T2 core 骨架——魔法道具内容批;设计权威 design/gdd/systems/排雷循环/魔法道具.md
|
||
# + registry aux_items api 8 入口/load_bearing_rules/use_orchestration)
|
||
# 魔法道具 关卡域库存 + 板级叠加层(自持派生信息层)。
|
||
# 结构冻结照抄(违反即返工):
|
||
# 零随机(零 RNG 流消费——信息只来自 排雷玩法 恒真数字/地图战斗资源 Ready 映射/行动经济 账本三既有事实源);
|
||
# 库存关卡域(跨轮跨图跨战斗延续、关卡末清空——清空编排归 战斗结算/关卡层,本类只供 clear/reset);
|
||
# 叠加层随图消亡(换图/弃图刷新/战斗结束三清——编排归 战斗结算 retire_overlays);
|
||
# Used 不可逆(consume 后保留只读——枚举序=受理序);
|
||
# 真视恒可用(零雷窗口=「这 9 格没雷」信息非空);点击级零信息不消耗(拒绝零消耗——预读窗口全已开);
|
||
# 零特判(排雷玩法 代码零本系统分支——本类不写任何其他系统状态)。
|
||
# 负空间:无自动使用入口/无重掷/无事件订阅/无 RNG 直连/无 排雷玩法/地图战斗资源 写入口;
|
||
# 相位检查归 战斗结算 use_aux 编排第一步(本类不管相位——validate_use 是纯谓词+投影)。
|
||
# 呈现取数面:query_inventory(C 区槽位)/query_overlays(棋盘叠加层标记)——只读投影,零数据复制
|
||
# (预读徽章数据源=战斗结算 query_reward_identity 既有读面,本类只记「哪些格要额外显示」)。
|
||
class_name AuxItems
|
||
extends RefCounted
|
||
|
||
var _items: Array = [] # [{handle, item_id, used}]——受理序;Used 保留只读
|
||
var _handle_seq := 0 # 受理序句柄(单调递增——零随机)
|
||
var _reveal_cells := {} # 真视集:Vector2i → true(惩罚格标记——插旗仍走既有 flag 路径)
|
||
var _preview_cells := {} # 预读集:Vector2i → true(奖励身份徽章呈现挂点)
|
||
var _last_reject := "" # 诊断面(最近一次拒绝原因——呈现反馈取数)
|
||
|
||
|
||
# ---- 生命周期 ----
|
||
|
||
# 关卡开始/关卡末清空(clear_inventory 幂等;关卡末清空前取 inventory_count() 快照做可见反馈)
|
||
func reset() -> void:
|
||
_items.clear()
|
||
_handle_seq = 0
|
||
retire_overlays()
|
||
|
||
|
||
# 授予(战斗结算 grant 路由——来源可归因:source_class+cell+round_index)
|
||
func add_item(item_id: String, source_class: String, cell: Vector2i, round_index: int) -> Dictionary:
|
||
_handle_seq += 1
|
||
var rec := {
|
||
"handle": _handle_seq,
|
||
"item_id": item_id,
|
||
"source_class": source_class,
|
||
"origin_cell": cell,
|
||
"round_index": round_index,
|
||
"used": false,
|
||
}
|
||
_items.append(rec)
|
||
return rec.duplicate()
|
||
|
||
|
||
# ---- 库存读面(C 区槽位常读)----
|
||
|
||
# 枚举序=受理序;Used 保留只读;逐件含 usable 板级零信息预判(调用方喂板面回调)
|
||
func query_inventory(board_has_preview_info: Callable) -> Array:
|
||
var out: Array = []
|
||
for rec in _items:
|
||
var row: Dictionary = rec.duplicate()
|
||
row["usable"] = _usable_now(String(rec.item_id), board_has_preview_info)
|
||
out.append(row)
|
||
return out
|
||
|
||
|
||
func inventory_count() -> int:
|
||
return _items.size()
|
||
|
||
|
||
# 板级零信息可用性预判(图标置灰/入口禁用判据——GDD「整张图都没东西可看」级):
|
||
# 真视恒可用(零雷窗口=信息非空);AP 类恒可用(组合判据取 OR——任一效果有信息即可用);
|
||
# 纯预读行 = 板上还有未开格才可用。
|
||
func _usable_now(item_id: String, board_has_preview_info: Callable) -> bool:
|
||
var row: Dictionary = _item_row(item_id)
|
||
if row.is_empty():
|
||
return false
|
||
var kinds: Array = row.get("effect_kinds", [])
|
||
if kinds.has("mine_reveal") or kinds.has("ap_grant"):
|
||
return true
|
||
return bool(board_has_preview_info.call()) if board_has_preview_info.is_valid() else true
|
||
|
||
|
||
func _item_row(item_id: String) -> Dictionary:
|
||
if _db != null and _db.aux_items != null:
|
||
return _db.aux_items.row_by_id(item_id)
|
||
return {}
|
||
|
||
var _db = null # ConfigDB(行定义读取——_init 注入;测试可注入桩表)
|
||
|
||
|
||
func _init(db_ref = null) -> void:
|
||
_db = db_ref
|
||
|
||
|
||
# ---- 使用校验(战斗结算 use_aux 编排第二步——纯谓词+投影,零消耗零副作用)----
|
||
|
||
# target = 目标格(空间行=3×3 窗口中心;ap 行忽略)。board_size = 板维度;
|
||
# cell_unrevealed_cb = Callable(Vector2i) -> bool(排雷玩法 只读面——预读窗口信息判据)。
|
||
# 返回 {ok, reason, resolved: Array[Vector2i], item_id, kinds}——ok=false 时 reason 可呈现。
|
||
func validate_use(handle: int, target: Vector2i, board_size: Vector2i, cell_unrevealed_cb: Callable) -> Dictionary:
|
||
var rec := _rec_by_handle(handle)
|
||
if rec.is_empty():
|
||
return _reject("句柄不存在")
|
||
if bool(rec.used):
|
||
return _reject("道具已使用(Used 不可逆)")
|
||
var row: Dictionary = _item_row(String(rec.item_id))
|
||
if row.is_empty() or bool(row.get("retired", false)) or bool(row.get("deferred", false)):
|
||
return _reject("道具未激活")
|
||
var kinds: Array = row.get("effect_kinds", [])
|
||
var resolved: Array = []
|
||
if kinds.has("mine_reveal") or kinds.has("reward_preview"):
|
||
# 空间效果:3×3 窗口 ∩ 板内格(边缘受理——护栏只缩不扩)
|
||
if target.x < 0 or target.y < 0 or target.x >= int(board_size.x) or target.y >= int(board_size.y):
|
||
return _reject("目标越板")
|
||
for dy in range(-1, 2):
|
||
for dx in range(-1, 2):
|
||
var c := target + Vector2i(dx, dy)
|
||
if c.x >= 0 and c.y >= 0 and c.x < int(board_size.x) and c.y < int(board_size.y):
|
||
resolved.append(c)
|
||
# 点击级零信息:纯预读行窗口全已开 = 没东西可看(拒绝零消耗);
|
||
# 真视恒可用(零雷窗口=「这 9 格没雷」信息非空——GDD 裁定)
|
||
if kinds.has("reward_preview") and not kinds.has("mine_reveal"):
|
||
var any_unrevealed := false
|
||
if cell_unrevealed_cb.is_valid():
|
||
for c in resolved:
|
||
if bool(cell_unrevealed_cb.call(c)):
|
||
any_unrevealed = true
|
||
break
|
||
if not any_unrevealed:
|
||
return _reject("窗口内无可预读的未开格")
|
||
return {
|
||
"ok": true,
|
||
"reason": "",
|
||
"resolved": resolved,
|
||
"item_id": String(rec.item_id),
|
||
"kinds": kinds,
|
||
"magnitude": int(row.get("magnitude", 0)),
|
||
}
|
||
|
||
|
||
# ---- 叠加层(战斗结算逐 kind 应用——空间先、ap 后的序在编排侧钉死)----
|
||
|
||
func apply_overlay(kind: String, cells: Array) -> void:
|
||
var store := _reveal_cells if kind == "mine_reveal" else _preview_cells
|
||
for c in cells:
|
||
store[Vector2i(c)] = true
|
||
|
||
|
||
# 三清只读投影(棋盘叠加层标记渲染——真视集/预读集;随图消亡由 retire_overlays 承载)
|
||
func query_overlays() -> Dictionary:
|
||
return {
|
||
"mine_reveal": _reveal_cells.keys(),
|
||
"reward_preview": _preview_cells.keys(),
|
||
}
|
||
|
||
|
||
# 换图/弃图刷新/战斗结束——叠加层清空(已花道具不返还;库存不动——库存关卡域)
|
||
func retire_overlays() -> void:
|
||
_reveal_cells.clear()
|
||
_preview_cells.clear()
|
||
|
||
|
||
# ---- 消耗(编排第四步——Used 不可逆+快照回执)----
|
||
|
||
func consume(handle: int) -> Dictionary:
|
||
var rec := _rec_by_handle(handle)
|
||
if rec.is_empty() or bool(rec.used):
|
||
return {}
|
||
rec.used = true
|
||
return rec.duplicate()
|
||
|
||
|
||
# ---- 内部 ----
|
||
|
||
func _rec_by_handle(handle: int) -> Dictionary:
|
||
for rec in _items:
|
||
if int(rec.handle) == handle:
|
||
return rec
|
||
return {}
|
||
|
||
|
||
func _reject(reason: String) -> Dictionary:
|
||
_last_reject = reason
|
||
return {"ok": false, "reason": reason, "resolved": [], "item_id": "", "kinds": [], "magnitude": 0}
|
||
|
||
|
||
# 探针出口(T7 收拢——断言面:库存序/叠加层集/拒绝原因)
|
||
func probe() -> Dictionary:
|
||
return {
|
||
"count": _items.size(),
|
||
"used": _items.filter(func(r): return bool(r.used)).size(),
|
||
"reveal_cells": _reveal_cells.size(),
|
||
"preview_cells": _preview_cells.size(),
|
||
"last_reject": _last_reject,
|
||
}
|