iter42-卡2: level_runner委托壳19枚拆除+shop公开化直连键7=33+残留零真残留+基线记账03/04/05(用户裁)

This commit is contained in:
DKChan committed 2026-10-30 01:34:06 +08:00
1 parent 66e6d0452a
commit 9c12d0a683
18 files changed
+92 -172

No files matched your search

+11 -91
View File
@@ -41,7 +41,7 @@ var hero_hp: int:
var gp: int: # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要)
get: return _wallet.gp
set(v): _wallet.gp = v
var _shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
var shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
# 协作对象私有(E1/E3 封装收口):settlement 读取经 current_battle() 成文出口
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(TreasureItems;跨战斗延续,
@@ -96,10 +96,10 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> v
_hero_armor_type = String(ctx.get("hero_armor_type", ""))
_voucher_rap = maxi(0, int(ctx.get("voucher_rap", 0)))
_voucher_time_sand = maxi(0, int(ctx.get("voucher_time_sand", 0)))
_shop = ShopSession.new(db, rng)
shop = ShopSession.new(db, rng)
var pool: Dictionary[Variant, Variant] = ctx.get("pool_wiring", {}) as Dictionary
if String(ctx.get("shop_mode", "")) == "pool" and not pool.is_empty():
_shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
pool.get("ticket", Callable()) as Callable, pool.get("cap", Callable()) as Callable)
_bind_shop()
@@ -302,8 +302,8 @@ func breakpoint_fragment() -> Dictionary[Variant, Variant]:
"node_id": current_node_id(),
"visited_ids": visited_ids(),
"aux_items": treasure_snapshot,
"shop_shelf": _shop.shop_bp_shelf(),
"shop_visit_buys": _shop.shop_bp_visit_buys(),
"shop_shelf": shop.shop_bp_shelf(),
"shop_visit_buys": shop.shop_bp_visit_buys(),
}
@@ -504,8 +504,8 @@ func _enter_node(node: Dictionary) -> bool:
# 商店房兑付:到达即开店(覆盖层归呈现层),购买出账/
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
var payload := {"gp": gp} # legacy 固定三件(off 对照承载——零 RNG 零池消费)
if _shop.pool_mode:
_shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
if shop.pool_mode:
shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
payload = {"gp": gp, "shelf": true}
node_entered.emit(node, payload)
return true
@@ -701,7 +701,7 @@ func continue_after_node() -> bool:
if t != "reward" and t != "rest" and t != "shop":
return false
if t == "shop":
_shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
return _present_choices()
@@ -721,12 +721,12 @@ func close_wallet() -> void:
# ---- 商店房兑付(GP+商店房实施批:呈现层只读投影 + 购买出账)----
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);本类保留同签名委托与件注入/
# 白盒入口——外部调用面与断点取数面零变化。
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);外部调用面=调用方经宿主公开
# 成员 shop 直连(调用直连不留委托壳——2026-10-26 Middle Man 裁定)。
# 件注入(建件与宿主引用换代时调用——钱包/主角账三件/图行/装配集为共享引用,出账直接落同一引用)
func _bind_shop() -> void:
_shop.bind({
shop.bind({
"wallet": _wallet,
"journal": _journal,
"level_row": level_row,
@@ -746,83 +746,3 @@ func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/关卡完成回血同式)
func _gain_max_hp_flat(flat: int) -> void:
_stats.hero_hp = mini(_stats.hero_hp + flat, _stats.effective_max_hp())
func shop_products() -> Array:
return _shop.shop_products()
func current_price(prow: Dictionary) -> int:
return _shop.current_price(prow)
func purchased_tier(product_id: String) -> int:
return _shop.purchased_tier(product_id)
func buy_product(product_id: String) -> bool:
return _shop.buy_product(product_id)
func player_request_refresh() -> bool:
return _shop.player_request_refresh()
# 散折价单点(测试取数面沿用本类调用名)
func _spend_discounted(price: int) -> int:
return _shop._spend_discounted(price)
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
# 池消费 handler + 专长凭证判定 + cap 检查回调(cap 满 kind 剔出货架候选视图)。
func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable(), cap_check: Callable = Callable()) -> void:
_shop.set_shop_pool(deck_ref, consume_handler, ticket_consider, cap_check)
# 货架抽签白盒入口(测试取数面沿用本类调用名)
func _shop_draw_shelf() -> void:
_shop._draw_shelf()
func pool_stock(kind: String) -> int:
return _shop.pool_stock(kind)
func pool_price(kind: String) -> int:
return _shop.pool_price(kind)
func shop_shelf() -> Array:
return _shop.shop_shelf()
func shop_refresh_free_left() -> int:
return _shop.shop_refresh_free_left()
func shop_refresh_cost() -> int:
return _shop.shop_refresh_cost()
func shop_refresh() -> bool:
return _shop.shop_refresh()
func spend_gp(amount: int) -> bool:
return _shop.spend_gp(amount)
func buy_pool_item(kind: String) -> bool:
return _shop.buy_pool_item(kind)
func is_shop_pool() -> bool:
return _shop.is_shop_pool()
func shop_bp_shelf() -> Array:
return _shop.shop_bp_shelf()
func shop_bp_visit_buys() -> int:
return _shop.shop_bp_visit_buys()
+1 -1
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 商店会话件:关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主。
## 钱包/主角账/装配集归宿主(GpWallet/GainJournal 共享引用经 bind 注入——GP 读写走钱包、购买加算走日记单点);
## Core 层零 UI 依赖;公开面由 LevelRunner 同签名委托承载(外部调用面零变化);
## Core 层零 UI 依赖;外部调用面=调用方经宿主 LevelRunner 公开成员 shop 直连(调用直连不留委托壳);
## legacy 模式零 RNG 零池消费(off 对照承载)。
class_name ShopSession
extends RefCounted
+12 -12
View File
@@ -170,37 +170,37 @@ func on_shop_buy(product_id: String) -> void:
var runner = _runner_get.call()
if runner == null:
return
if runner.is_shop_pool():
if runner.shop.is_shop_pool():
# pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用)
if not runner.buy_pool_item(product_id):
if not runner.shop.buy_pool_item(product_id):
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
toast.show_toast("GP不足——买不起这件商品", "warn")
return
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
shop_overlay.open_shelf(runner.shop.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop.shop_refresh_free_left(), runner.shop.shop_refresh_cost(), int(runner.gp) >= runner.shop.shop_refresh_cost())
_refresh_presentations.call()
return
if not runner.buy_product(product_id):
if not runner.shop.buy_product(product_id):
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
toast.show_toast("GP不足——买不起这件商品", "warn")
return
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
shop_overlay.refresh_state(runner.shop.shop_products(), int(runner.gp))
_refresh_presentations.call()
# MC-1:刷新意图(shop_overlay.refresh_requested——核心 shop_refresh 受理后整架重投喂=重抽反馈)
func on_shop_refresh() -> void:
var runner = _runner_get.call()
if runner == null or not runner.is_shop_pool():
if runner == null or not runner.shop.is_shop_pool():
return
if not runner.shop_refresh():
if not runner.shop.shop_refresh():
toast.show_toast("GP不足——刷新失败", "warn")
return
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
shop_overlay.open_shelf(runner.shop.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop.shop_refresh_free_left(), runner.shop.shop_refresh_cost(), int(runner.gp) >= runner.shop.shop_refresh_cost())
_refresh_presentations.call()
@@ -160,7 +160,7 @@ func on_refresh() -> void:
if not refreshed:
# 免费位(X/Y)已穷——GP 付费档(先扣钱包后重抽,余额不足拒绝零副作用)
var cost: int = adventure.refresh_bank().gp_cost()
if runner == null or not runner.spend_gp(cost):
if runner == null or not runner.shop.spend_gp(cost):
ctx.views.toast.show_toast("GP不足——刷新失败", "warn")
return
refreshed = adventure.stop().refresh_curio_paid()
+1 -1
View File
@@ -770,7 +770,7 @@ func _on_refresh_confirmed() -> void:
return
var ok: bool = false
if run_scope.level_active and run_scope.runner != null:
ok = run_scope.runner.player_request_refresh()
ok = run_scope.runner.shop.player_request_refresh()
elif run_scope.settlement != null:
ok = run_scope.settlement.player_request_refresh()
logger.log_action("refresh", Vector2i(-1, -1), ok)
+5 -5
View File
@@ -78,12 +78,12 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。
# pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载)
root._open_modal(root.shop_overlay)
if root.run_scope.runner.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.shop_overlay.set_refresh_display(root.run_scope.runner.shop_refresh_free_left(), root.run_scope.runner.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop_refresh_cost()) # MC-1
if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1
else:
root.shop_overlay.open_shop(root.run_scope.runner.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
root.logger.log_node(node, info, root.run_scope.runner.node_index)
+2 -2
View File
@@ -132,10 +132,10 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic
if String(event) == "gain":
root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain)
if root.shop_overlay.visible and root.run_scope.runner != null:
if root.run_scope.runner.is_shop_pool():
if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else:
root.shop_overlay.refresh_state(root.run_scope.runner.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root._refresh_presentations()
+2 -2
View File
@@ -85,7 +85,7 @@ func _xnum(v: float) -> String:
return "×%d" % int(round(v)) if absf(v - roundf(v)) < 0.005 else "×%.2f" % v
# 打开商店(products = runner.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
# 打开商店(products = runner.shop.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
func open_shop(products: Array, gp: int) -> void:
for child in _cards_box.get_children():
child.queue_free()
@@ -149,7 +149,7 @@ func _emit_buy_requested(p: Dictionary) -> void:
# ---- 共享池货架面(M-4 迭代 11 第②批 T4——P3 甲程序占位,三选一同构卡;UiKit 既有件)----
# 打开/重刷货架(cards = runner.shop_shelf() 投影——[{kind_id, display_name, value, price, stock_left}])。
# 打开/重刷货架(cards = runner.shop.shop_shelf() 投影——[{kind_id, display_name, value, price, stock_left}])。
# 买后组合根重投喂新货架 = 整架重建呈现(三件全重抽反馈——未买不保留);空架 = 池空硬闸合法态。
func open_shelf(cards: Array, gp: int) -> void:
for child in _cards_box.get_children():
+8 -8
View File
@@ -332,13 +332,13 @@ static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr
# 商店贪心买齐:GP 局内死钱——买得起就买( affordable 集里挑最便宜的
# 反复买到达不上任何商品为止,guard 防死循环)。确定性(零随机流);购买出账/加算区入账全走
# runner.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。
# runner.shop.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。
static func _autoplay_shop(r, st: Dictionary) -> void:
var guard := 30
while guard > 0:
guard -= 1
var affordable: Array = []
for p in r.shop_products():
for p in r.shop.shop_products():
if r.gp >= int(p.get("price", 1 << 30)):
affordable.append(p)
if affordable.is_empty():
@@ -347,20 +347,20 @@ static func _autoplay_shop(r, st: Dictionary) -> void:
for p in affordable:
if int(p.get("price", 1 << 30)) < int(pick.get("price", 1 << 30)):
pick = p
if not r.buy_product(String(pick.get("product_id", ""))):
if not r.shop.buy_product(String(pick.get("product_id", ""))):
return
st.shop_buys = int(st.get("shop_buys", 0)) + 1
# 商店共享池贪心:货架三选一同构面贪心买齐——affordable 集挑最便宜反复买
# (每次买后全重抽、货架随买随换,枯竭/不可负担即止,guard 防死循环)。购买走 runner.buy_pool_item
# (每次买后全重抽、货架随买随换,枯竭/不可负担即止,guard 防死循环)。购买走 runner.shop.buy_pool_item
# 核心编排(与呈现层购买同一出账+池消费路径,wallet_changed 信号照常采集)。确定性(零随机流)。
static func _autoplay_shop_pool(r, st: Dictionary) -> void:
var guard := 30
while guard > 0:
guard -= 1
var affordable: Array = []
for c in r.shop_shelf():
for c in r.shop.shop_shelf():
if r.gp >= int(c.get("price", 1 << 30)):
affordable.append(c)
if affordable.is_empty():
@@ -369,7 +369,7 @@ static func _autoplay_shop_pool(r, st: Dictionary) -> void:
for c in affordable:
if int(c.get("price", 1 << 30)) < int(pick.get("price", 1 << 30)):
pick = c
if not r.buy_pool_item(String(pick.get("kind_id", ""))):
if not r.shop.buy_pool_item(String(pick.get("kind_id", ""))):
return
st.shop_buys = int(st.get("shop_buys", 0)) + 1
@@ -415,10 +415,10 @@ static func _route_pick(runner, route: String, st: Dictionary) -> int:
s = 10.0
var cheapest := 1 << 30
if String(st.get("shop_mode", "legacy")) == "pool":
for c in runner.shop_shelf(): # pool 面:货架现价
for c in runner.shop.shop_shelf(): # pool 面:货架现价
cheapest = mini(cheapest, int(c.get("price", 1 << 30)))
else:
for p in runner.shop_products():
for p in runner.shop.shop_products():
cheapest = mini(cheapest, int(p.get("price", 1 << 30)))
if cheapest < (1 << 30) and runner.gp >= cheapest:
s = 80.0
+7 -7
View File
@@ -1,5 +1,5 @@
{
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿);2026-10-26 用户特批带红合并(迭代 38 收口):09 静态扫描记存量红——gdstyle 恶化 2 条(battle_orchestrator.gd max-file-length 1314>1302=卡④头注必载内容、level_map_screen.gd max-class-variables 22>21=卡①缓存成员,均为卡片义务的记账漂移非缺陷,压缩还债经计算不可行),用户裁「不管引入的恶化直接提交、特许免测试重跑」;后续批次若触及两文件可顺带还债或经批准重生成基线归绿;2026-10-27 用户裁「标准是尺、不以现状为尺」:09 存量红照实记(exit=1+现行红项指纹,两条恶化各自点名可核),gdstyle 基线登记值回退 1302/21(波 0 的 18 条陈旧键清理保留),gdstyle_gate 逐条违规行加 FAIL 前缀并加「refresh 禁抬高登记值」守卫——存量红期间任何新增违规或进一步恶化都会改指纹被拦;阈值口径不变=gdstyle 默认值(文件 1000 行/类变量 15 等),还债归迭代 40 卡①(battle_orchestrator 1314→≤1000)与迭代 41 卡③(level_map_screen 22→≤15),还清后重新生成基线归绿;2026-10-27 用户特批带红合并(迭代39 拆件批):02 记存量红(fp f74b9a48ef4a=round_flow EV 锚 goblin 0.9148∉[1.00,1.20],触发器批有意行为改动,旧带宽锚触发器前设计,重锚归后续数值批待用户发起);08d 记存量红(red-nokeys=生涯.md:55 引 adventure_runner.gd:814 行号漂移,B1 拆分后 660 行,另 :50/:60 与 元进度.md:70 建议级,修复归合并后文档批,反哺点在册);07 doc_link 基线红→现绿改善随下次基线重生成记账;09 指纹经 S9 注解修复(f96632de)回归登记值 91d45583cf6d;2026-10-28 迭代40 收口记账(用户裁「刷」):卡① 拆件还债——battle_orchestrator 行数红项消失(实测 1036≤登记 1302;契约2 残余 36 行系合并适配带入 master 迭代39 内容,用户裁登记残余债归待办清单),09 红项由两条收敛为一条(level_map_screen 类变量 22>21=迭代38 存量,还债归迭代41 卡③),指纹 91d45583cf6d → 8467b4588a20(收口会话按闸壳同款算法对用户探跑日志实算,全量闸复验);02 存量红已随 master 侧 EV 重锚批(ba5105aa)归绿并入本合并;2026-10-28 EV 锚重锚批(工程改动·worktree ev-anchor-remeasure-20261028,用户特许会话内执行):02 基线红→现绿归绿——round_flow EV 锚 goblin 带宽按无情境口径(模拟器不过滤 trigger 行)重锚 [0.82,1.02](迭代39触发器批孤注守势入池稀释,实测 0.9148,重锚注记 enemy.tres 与测试注释;旧带 [1.00,1.20] 随池构成换代作废),撤销证明=带宽回退 [1.00,1.20] 断言复红;08d 存量红不变(生涯.md:55 行号漂移,修复归文档批);2026-10-29 文档清理批(批 5)实测 --docs 域 07/08d 皆绿,两条目归绿;2026-10-29 能力审计闸上电(tools/capability_audit.py 注册文档域 08e,查 GDD 纯度+能力清单与覆盖域表结构):手工加入绿记录(批 5 纯度红项清零后孤立实测 exit=0),其余条目原样保留;2026-10-29 合回闸关联档批实测重生成 02/09 两键(人工确认凭据=待办清单「迭代 41 测试债修复」条 2026-10-29 用户裁带红合并、代码域现存红全部由该条目承接):02 记存量红 fp f9865f1a5a44=9 套件失败(config_db 解析错 cattr/config_db_fatal_oob 退出码 2≠1/table_structure_guard 缺 meta_state/treasure_items 缺 crossrun/standee_fx_sweep set_trans 空引用/forge_workbench 120s 超时挂死/tree_requires_level 空基址/save_manager_domain 缺 _Line truncated
"note": "存量基线·闸况变化时本脚本会判红,需人工确认后重生成;2026-10-29 二次记账=03/04/05 三闸按 80b49c0f 先例经用户裁决记入现行红项指纹(03=8a3cae0d0446/04=591b27250b36/05=de89589e49ed)——master 同值复现非迭代42新增(stash 对照法取证,承「迭代 41 测试债修复」条目扩展);原 2026-10-17 06 条目人工重生成记录见 git 提交史",
"gates": {
"00 冒烟测试": {
"exit": "0",
@@ -14,16 +14,16 @@
"fp": "f9865f1a5a44"
},
"03 探针 --click": {
"exit": "0",
"fp": "green"
"exit": "1",
"fp": "8a3cae0d0446"
},
"04 探针 --full": {
"exit": "0",
"fp": "green"
"exit": "1",
"fp": "591b27250b36"
},
"05 渲染闸": {
"exit": "0",
"fp": "green"
"exit": "1",
"fp": "de89589e49ed"
},
"06 gdd_lint": {
"exit": "0",
+1 -1
View File
@@ -361,7 +361,7 @@ func _smoke_adventure() -> void:
if int(pb.get("level_index", -1)) == 2:
l2_price_bonus = int(pb.get("bonus", 0))
Kit.check("进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus], gp0 >= hp_price + l2_price_bonus)
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.buy_product("hp_up"))
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.shop.buy_product("hp_up"))
var acc_shop: int = a8.journal().bonus_total("max_hp") # L1 三选一后 + 商店 3(首件随种子)
Kit.check("统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop, acc_shop >= 3)
var l2_cap := {"payload": {}} # 公开信号面采集(curio_choice_presented 停点载荷——容器写回规避 lambda 按值捕获)
@@ -49,9 +49,9 @@ func _init() -> void:
# ---- ⑤ 钱包单口支出 spend_gp(LevelRunner——三选一付费档与商店刷新共用同一冒险钱包)----
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.gp = 30
Kit.check_quiet(lr.spend_gp(0) == false and int(lr.gp) == 30, "零额支出拒绝零副作用")
Kit.check_quiet(lr.spend_gp(31) == false and int(lr.gp) == 30, "余额不足拒绝零副作用")
Kit.check_quiet(lr.spend_gp(20) == true and int(lr.gp) == 10, "足额支出扣账(30→10)")
Kit.check_quiet(lr.shop.spend_gp(0) == false and int(lr.gp) == 30, "零额支出拒绝零副作用")
Kit.check_quiet(lr.shop.spend_gp(31) == false and int(lr.gp) == 30, "余额不足拒绝零副作用")
Kit.check_quiet(lr.shop.spend_gp(20) == true and int(lr.gp) == 10, "足额支出扣账(30→10)")
# ---- ⑥ Y 断点随存(缺陷锚:续玩自参数面整额重注入=用完 Y 退菜单再续玩满血复活)----
var bp_y := {"level_index": 1, "hero_hp": 10, "attune_refresh_y": 2, "graph_gen_v2": 1}
+2 -2
View File
@@ -57,13 +57,13 @@ func _init() -> void:
# ---- hr1-s:商店属性商品加算走日记单写原语(购买后 bonus_total 与旧径同值)----
runner._gain_wallet(1000) # 钱包注入(测试直调写口——测试构造惯例)
var products: Array = runner.shop_products()
var products: Array = runner.shop.shop_products()
var bought := false
for p in products:
if int(p.get("price", 1 << 30)) <= 1000 and String(p.get("attr", "")) != "":
var attr := String(p.get("attr", ""))
var before: int = runner.journal().bonus_total(attr)
bought = runner.buy_product(String(p.get("product_id", "")))
bought = runner.shop.buy_product(String(p.get("product_id", "")))
if bought:
Kit.check_quiet(runner.journal().bonus_total(attr) == before + int(p.get("flat_value", 0)),
"hr1-s 商店购买后 bonus_total 增量 = 商品 flat(加算走 gain_bonus 单写原语——同值承载)")
+27 -27
View File
@@ -1025,17 +1025,17 @@ func _smoke_shop_room() -> void:
return
Kit.check("到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)", not runner.awaiting_choice() and runner.next_candidate_ids().is_empty())
# 现价 = 基价 + price_level_bonus(L2)=2 + 步长 × 已购档数
var prods: Array = runner.shop_products()
var prods: Array = runner.shop.shop_products()
Kit.check("三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)", prods.size() == 3)
var by_id := _shop_products_by_id(prods)
Kit.check("L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)", int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6)
# 购买 atk_up:钱包 −6 · 加算区 atk +1 · 二购现价 8
var gp0: int = runner.gp
Kit.check("购买 atk_up 受理(现价 6)", runner.buy_product("atk_up"))
Kit.check("购买 atk_up 受理(现价 6)", runner.shop.buy_product("atk_up"))
Kit.check("钱包扣现价 6(%d → %d)" % [gp0, runner.gp], runner.gp == gp0 - 6)
# 加算区住加算日记
Kit.check("统一冒险加算区 atk +1(加算区住加算日记)", runner.journal().bonus_total("atk") == 1 and runner.journal().bonus_total("max_hp") == 0 and runner.journal().bonus_total("armor_class") == 0)
by_id = _shop_products_by_id(runner.shop_products())
by_id = _shop_products_by_id(runner.shop.shop_products())
Kit.check("atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)", int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1)
# 行进段百分比归零(购买/生效断言按加算区裸值口径——调试浮窗百分比后乘;hp 快照钳到新口径上限防 spawn 越界,测试通道)
runner.set_hero_override(0, 0)
@@ -1043,28 +1043,28 @@ func _smoke_shop_room() -> void:
# hp_up:上限与当前生命同步 +3(封顶新上限)
var hp_before: int = runner.hero_hp
var mhp_before: int = runner.effective_max_hp()
Kit.check("购买 hp_up 受理(现价 4)", runner.buy_product("hp_up"))
Kit.check("购买 hp_up 受理(现价 4)", runner.shop.buy_product("hp_up"))
Kit.check("钱包扣 4(%d)" % runner.gp, runner.gp == gp0 - 6 - 4)
Kit.check("生命上限 +3(%d → %d——加算区 max_hp 轴)" % [mhp_before, runner.effective_max_hp()], runner.effective_max_hp() == mhp_before + 3)
Kit.check("当前生命同步 +3 封顶新上限(%d → %d)" % [hp_before, runner.hero_hp], runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()))
# armor_up:AC 10+1
Kit.check("购买 armor_up 受理(现价 6)", runner.buy_product("armor_up"))
Kit.check("购买 armor_up 受理(现价 6)", runner.shop.buy_product("armor_up"))
Kit.check("AC = 10+1(armor_class 加算区轴,无百分比通道)", runner.effective_ac() == int(_db.combat.hero.armor_class) + 1)
# 余额不足购买被拒(零副作用):hp_up 连购至不可负担后兜底拒绝
var guard_drain := 20
var hp_buys := 1 # 夹具前段已购 1 笔(购买 hp_up 受理断言)——总数 = 前段 + 本循环
while guard_drain > 0:
guard_drain -= 1
var hp_price: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
var hp_price: int = int(_shop_products_by_id(runner.shop.shop_products())["hp_up"]["price"])
if runner.gp < hp_price:
break
if not runner.buy_product("hp_up"):
if not runner.shop.buy_product("hp_up"):
break
hp_buys += 1
var gp_dry: int = runner.gp
var tier_dry: int = runner.purchased_tier("hp_up")
var hp_dry: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
Kit.check("余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" % [gp_dry, hp_dry], not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry)
var tier_dry: int = runner.shop.purchased_tier("hp_up")
var hp_dry: int = int(_shop_products_by_id(runner.shop.shop_products())["hp_up"]["price"])
Kit.check("余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" % [gp_dry, hp_dry], not runner.shop.buy_product("hp_up") and runner.gp == gp_dry and runner.shop.purchased_tier("hp_up") == tier_dry)
# 离开 → 回选路口(候选 = 该店出边——恰一条,指向终点 Boss)
Kit.check("离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" % str(shop_outs),
runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs))
@@ -1253,7 +1253,7 @@ func _smoke_shop_pool() -> void:
if not _adv_drive_to_shop(a8, _adv_st(), autoplay):
continue
scen = {"a": a8, "s": s, "r": a8.runner()}
first_shelf_a = (a8.runner().shop_shelf() as Array).duplicate(true)
first_shelf_a = (a8.runner().shop.shop_shelf() as Array).duplicate(true)
break
Kit.check("池模式商店房场景可达(seed=%s)" % str(scen.get("s", -1)), not scen.is_empty())
if not scen.is_empty():
@@ -1262,19 +1262,19 @@ func _smoke_shop_pool() -> void:
a8b.set_hero_override(900, 400)
if a8b.start_adventure(int(scen["s"]), {"shop_mode": "pool"}) \
and _adv_drive_to_shop(a8b, _adv_st(), autoplay):
first_shelf_b = (a8b.runner().shop_shelf() as Array).duplicate(true)
first_shelf_b = (a8b.runner().shop.shop_shelf() as Array).duplicate(true)
Kit.check("进店抽 3(货架 %d 卡——同构交互)" % first_shelf_a.size(), first_shelf_a.size() == 3)
# MC-1 商店独立刷新(P-C2+Q2 裁——基础免费 0;GP 刷新 ceil(20×1.8^n) provisional;重抽同源)
if not scen.is_empty():
var r8 = scen["r"]
var gp0: int = int(r8.gp)
var cost0: int = r8.shop_refresh_cost()
var cost0: int = r8.shop.shop_refresh_cost()
Kit.check("刷新首价=基价(%d)" % cost0, cost0 == int(_db.run.adventure.shop_refresh_cost_base))
Kit.check("基础免费 0 次(Q2——天赋增益位后续添加)", r8.shop_refresh_free_left() == 0)
var shelf_pre: Array = (r8.shop_shelf() as Array).duplicate(true)
Kit.check("GP 刷新受理(gp=%d cost=%d)" % [gp0, cost0], r8.shop_refresh())
Kit.check("基础免费 0 次(Q2——天赋增益位后续添加)", r8.shop.shop_refresh_free_left() == 0)
var shelf_pre: Array = (r8.shop.shop_shelf() as Array).duplicate(true)
Kit.check("GP 刷新受理(gp=%d cost=%d)" % [gp0, cost0], r8.shop.shop_refresh())
Kit.check("GP 刷新扣费(%d→%d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - cost0)
Kit.check("刷新爬价 ×curve(%d)" % int(r8.shop_refresh_cost()), int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.run.adventure.shop_refresh_cost_curve))))
Kit.check("刷新爬价 ×curve(%d)" % int(r8.shop.shop_refresh_cost()), int(r8.shop.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.run.adventure.shop_refresh_cost_curve))))
var same_shelf := first_shelf_a.size() == first_shelf_b.size()
if same_shelf:
for i in first_shelf_a.size():
@@ -1290,10 +1290,10 @@ func _smoke_shop_pool() -> void:
# ② 货架投影形制:kind 词表封闭 + 现价公式推导(ceil(base×b^(关序−1)×A^0)——核心现算,呈现零复算)
var l2 := int(r8.level_row.level_index)
# 现价等价证明(d11——消费端读表推导 vs 迁移前硬编码字面量推导:同输入逐位一致)
var relic_price_now: int = r8.pool_price("relic")
var relic_price_now: int = r8.shop.pool_price("relic")
var relic_price_pre: int = int(ceil(15.0 * pow(_db.run.shop.price_b, float(l2 - 1)) * 1.0))
Kit.check("relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" % relic_price_now, relic_price_now == relic_price_pre)
for c in r8.shop_shelf():
for c in r8.shop.shop_shelf():
# T6 商店保底后 relic 卡合法入架(保底占位)——词表断言放行 relic;价格断言分叉
# (数值卡走 shop_pool_row 推导 / relic 卡走表 knob relic_base_price——同公式不同基价)
var ckind := String(c.get("kind_id", ""))
@@ -1308,7 +1308,7 @@ func _smoke_shop_pool() -> void:
Kit.check("首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" % [ckind, int(c.get("price", -1)), expect_p], int(c.get("price", -1)) == expect_p)
# ③ 买 1 全重抽:出账+加算区+账本三落点 + 现价 A 链递增(买后 visit 计数=1;TP0——消费在架令牌本体)
var buy_c: Dictionary = {}
for c in r8.shop_shelf():
for c in r8.shop.shop_shelf():
if int(c.get("price", 1 << 30)) <= int(r8.gp):
buy_c = c
break
@@ -1320,7 +1320,7 @@ func _smoke_shop_pool() -> void:
var gp0: int = int(r8.gp)
var taken0: int = int(a8._curio.taken.get(kind, 0))
var val0: int = a8.journal().bonus_total(String(val_map[kind]))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.buy_pool_item(kind))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.shop.buy_pool_item(kind))
Kit.check("钱包扣现价(%d → %d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - int(buy_c.get("price", 0)))
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.run.adventure.curio_value(kind, tier))], a8.journal().bonus_total(String(val_map[kind])) == val0 + int(_db.run.adventure.curio_value(kind, tier)))
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8.deck().taken.get(kind, 0))], int(a8.deck().taken.get(kind, 0)) == taken0 + 1)
@@ -1328,7 +1328,7 @@ func _smoke_shop_pool() -> void:
# A 链:白盒抬 GP 保证第二购场景确定构造(直改测试基建——非生产行为)
r8.gp = int(r8.gp) + 64
var second: Dictionary = {}
for c in r8.shop_shelf():
for c in r8.shop.shop_shelf():
if int(c.get("price", 1 << 30)) <= int(r8.gp):
second = c
break
@@ -1336,14 +1336,14 @@ func _smoke_shop_pool() -> void:
var prow2: Dictionary = _db.run.shop.pool_row(String(second.get("kind_id", "")))
var expect_p2: int = int(ceil(float(int(prow2.get("base_price", 0))) * pow(_db.run.shop.price_b, float(l2 - 1)) * _db.run.shop.price_a))
Kit.check("A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" % expect_p2, int(second.get("price", -1)) == expect_p2)
Kit.check("同 visit 第二购受理(A 链软闸在价不在限)", r8.buy_pool_item(String(second.get("kind_id", ""))))
Kit.check("同 visit 第二购受理(A 链软闸在价不在限)", r8.shop.buy_pool_item(String(second.get("kind_id", ""))))
else:
Kit.check("A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败", false)
# ④ 切边零入店(白-box 重抽 40 次——GP 袋/规则改写型特性永不在架;重抽消费 shop_draw 签,场景尾不回写主时间线)
var cut_seen := false
for i in 40:
r8._shop_draw_shelf()
for c in r8.shop_shelf():
r8.shop._draw_shelf()
for c in r8.shop.shop_shelf():
if String(c.get("kind_id", "")) == "gp" or String(c.get("kind_id", "")) == "treasure_item":
cut_seen = true
Kit.check("切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)", not cut_seen)
@@ -1353,8 +1353,8 @@ func _smoke_shop_pool() -> void:
a8._curio.deck.remove_at(di8)
var attack_seen := false
for i in 40:
r8._shop_draw_shelf()
if (r8.shop_shelf() as Array).any(func(c): return String(c.get("kind_id", "")) == "attack"):
r8.shop._draw_shelf()
if (r8.shop.shop_shelf() as Array).any(func(c): return String(c.get("kind_id", "")) == "attack"):
attack_seen = true
break
Kit.check("库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)", not attack_seen)
@@ -732,12 +732,12 @@ func _smoke_tree_rules() -> void:
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004),
{"tree_rules": []}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散未激活:商品标价原价(无 economy_spend 键)",
int(lr.current_price({"base_price": 100, "price_increment": 0})) == 100 + int(_db.run.shop.price_level_bonus_at(0)))
int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == 100 + int(_db.run.shop.price_level_bonus_at(0)))
lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777005),
{"tree_rules": ["economy_spend"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)",
int(lr.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount))))
and int(lr.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount)))))
int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount))))
and int(lr.shop.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount)))))
# 财富之道·聚:战斗掉落乘算·入账前(编排 _roll_gp_drop——同种子配对对拍 1.5×)
var orch3 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005))
Kit.check("聚 rig:基线编排启动(无增益侧)",
+3 -3
View File
@@ -42,9 +42,9 @@ func _init() -> void:
Kit.check_quiet(not e.stop().skip_empty_curio(),
"空配置闸反例:非空停点拒绝跳过(零自动推进契约不破)〔专长批 T10〕")
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.set_shop_pool([], Callable(), Callable(), Callable())
lr._shop_draw_shelf()
Kit.check_quiet((lr.shop_bp_shelf() as Array).is_empty(),
lr.shop.set_shop_pool([], Callable(), Callable(), Callable())
lr.shop._draw_shelf()
Kit.check_quiet((lr.shop.shop_bp_shelf() as Array).is_empty(),
"空配置闸:商店空货架=合法态(牌库空=零扰动不崩)〔专长批 T10〕")
quit(Kit.finish("pool_case_d"))
@@ -226,11 +226,11 @@ func _init() -> void:
# ⑩ 刷新执照(商店免费刷新 +2/店——读数叠加)
var lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free),
Kit.check_quiet(int(lv10.shop.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free),
"刷新执照基线:未装配=表值(%d)" % int(db.run.adventure.shop_refresh_free))
lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new(),
{"attunement": ["refresh_license"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free) + 2,
Kit.check_quiet(int(lv10.shop.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free) + 2,
"刷新执照正常:免费刷新 +2/店(读数即本店可用——进店重置生命周期同 knob)")
# ⑪ 天眼(3×3 真视+安全格直开/危险格不开/封印格跳过)
+1 -1
View File
@@ -3,7 +3,7 @@
# 在册词表组合;本测试直构 rebuild 同构合成行(与 effect_engine.gd rebuild 树规则段逐字段同源)
# 调 validate_trigger_row 断零违例。实现侧扩闭集未呈报时,本断言以在册词表为准转红。
# 聚散两键(economy_hoard/economy_spend)不入事件面:消费点=tree_rules 成员判定
# (battle_orchestrator._roll_gp_drop GP 掉落乘算 / level_runner._spend_discounted 商品折价),
# (battle_orchestrator._roll_gp_drop GP 掉落乘算 / shop_session._spend_discounted 商品折价),
# rebuild 树规则段对两键零合成行——此状态在此被显式断言(旧版对两键取空夹具静默跳过=假绿):
# rebuild 实装面行数对照——四键逐键与夹具同行数(rebuild 分支被删/漂移即转红),
# 两键断零行(将来被加合成行即转红,提示先复核效果闭集再同步夹具与实现)。