iter56-卡1:结算期文字列表退役与日志零文案(迭代56卡1): 撤B区逐行文字列表(row_pacer文字入带三调用/行格式化域删净——FX排播链零diff保留)+日志step行零文案(text/display_name不落盘)+reward_ineffective六发射点补reason结构化(full_layers/full_hp/no_enemy_alive/hero_down); 分析工具改读结构化字段(target==all_enemies/reason优先text兜底双分支)+analyze_parity_check对拍载体。读数: R1零逐行文字/R2 FX链行型分布独立推导逐位一致/R3零文案+全字段落盘/R4判读等价三读数全一致/R5四类reason逐一命中——各T提交信息载; 能力行:137/:138名实一致(_smoke_settle_zero_rows/_assert_fx_chain/_assert_no_text_and_full_payload/_assert_four_reasons); 测试削弱全部申报内(§10清单+§5.5同生死波及面11处登记实现报告); 中档审计通过
This commit is contained in:
1 parent
9251d35650
commit
908e518306
40 files changed
+1258
-1298
No files matched your search
@@ -0,0 +1,105 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 结算期 B 区零逐行文字断言(逐行结算排播退役——08-Alpha v1.29 删「逐行结算排播」行、
|
||||
# 用户裁定 2026-11-07 撤逐行滚动文字列表保留伤害飘字与攻击特效):
|
||||
# ① 真实结算产生逐行后(排播队列非空前提),B 区 RichTextLabel 行文本恒空(零逐行追加);
|
||||
# ② 排播泵消费期间与 flush 排空后 parsed_text 恒空——step_row 消费零产生 append_text 行;
|
||||
# ③ FX 排播链仍在(伤害行驱动演出层弹道/数字——演出不随文字退役,〔退役改 V-退役卡 R1〕)。
|
||||
# 载体:main.tscn 实例(组合根全链在座),真实冒险驱动至首战结算后断言(同 pacing_queue_test 形态)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/battle_log_zero_text_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
var _problems: Array = []
|
||||
|
||||
|
||||
func check(cond: bool, msg: String) -> void:
|
||||
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
|
||||
if not cond:
|
||||
_problems.append(msg)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# 判据出处: design/gdd/08-Alpha版本标准.md:154 战斗演出链(弹道 / 受击 / 血条 / 灰化)在役;
|
||||
# 逐行结算排播行已随 v1.29 退役——结算期 B 区零逐行文字、FX 排播链保持
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
for p in ["user://test_log_zero_text_save.json", "user://test_log_zero_text_save.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
sm.slots.attach_path("user://test_log_zero_text_save.json")
|
||||
sm.meta_state.mark_first_adventure_done()
|
||||
main._start_adventure_from_menu()
|
||||
await process_frame
|
||||
if not main.ui.modals.run.level_screen.visible:
|
||||
_problems.append("入口停点未呈现(前置失守)")
|
||||
_finish()
|
||||
return
|
||||
main.run_scope.runner.pick_next_node(int(main.run_scope.runner.next_candidate_ids()[0]))
|
||||
await process_frame
|
||||
await process_frame
|
||||
var battle = main.run_scope.runner.current_battle()
|
||||
if battle == null:
|
||||
_problems.append("首战未开打(零文字断言无载体)")
|
||||
_finish()
|
||||
return
|
||||
var rows_seen: Array = []
|
||||
battle.step_row.connect(_count_row.bind(rows_seen))
|
||||
# 理性策略挖掘至受理一动作(auto_player 单一实现复用——同 pacing_queue_test 两步消费)
|
||||
var ms = battle.minesweeper
|
||||
var ap = load("res://sim/auto_player.gd")
|
||||
var acted := false
|
||||
for guard in range(40):
|
||||
var plan: Dictionary = ap.choose_dig(ms)
|
||||
if plan.is_empty():
|
||||
break
|
||||
if plan.hero_cell != ms.hero_pos():
|
||||
ms.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||
if battle.action_economy.phase != "Active":
|
||||
break
|
||||
var outcomes: Array = ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||
acted = not outcomes.is_empty() and bool(outcomes[0].accepted)
|
||||
if acted:
|
||||
break
|
||||
if battle.action_economy.phase == "Active":
|
||||
ms.settle_batch([{"action": "stop"}])
|
||||
if not acted:
|
||||
_problems.append("首战无受理动作(零文字断言无行)")
|
||||
_finish()
|
||||
return
|
||||
await process_frame
|
||||
check(rows_seen.size() > 0, "结算行已产生(断言前提:%d 行)" % rows_seen.size())
|
||||
var log_view = main.ui.battle.log_view
|
||||
# 判据绑定面 = append_text 调用计数=0(「不绑终屏态」):进节点叙述行(node_banner,真实入口
|
||||
# 既有叙述面、非 step_row 消费产物)落带后采基线,断言 step_row 消费全程文本冻结零追加。
|
||||
var base_text: String = log_view.parsed_text()
|
||||
# ---- 时点一:行已入队、排播泵消费中——零新增行 ----
|
||||
check(log_view.parsed_text() == base_text, "结算消费中 B 区零新增行(时点一:逐行追加零发生)〔退役改 V-退役卡 R1〕")
|
||||
await process_frame
|
||||
await process_frame
|
||||
# ---- 时点二:泵已消费若干行——仍零新增(文字入带调用已撤,泵只驱动演出链)----
|
||||
check(log_view.parsed_text() == base_text, "泵消费数帧后 B 区零新增行(时点二)〔退役改 V-退役卡 R1〕")
|
||||
# ---- flush 同步排空(模态前置契约路径)后再采——仍零新增 ----
|
||||
main.ui.screens.row_pacer.flush()
|
||||
check(main.ui.screens.row_pacer.pending_rows().is_empty(), "flush 后队列排空(完整性前提)")
|
||||
check(log_view.parsed_text() == base_text, "排空后 B 区零新增行(时点三:step_row 消费 append_text 计数=0)〔退役改 V-退役卡 R1〕")
|
||||
_finish()
|
||||
|
||||
|
||||
func _count_row(row: Dictionary, arr: Array) -> void:
|
||||
arr.append(row.duplicate(true))
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
|
||||
for p in ["user://test_log_zero_text_save.json", "user://test_log_zero_text_save.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
if _problems.is_empty():
|
||||
print("[battle_log_zero_text_test] PASS — 结算期 B 区零逐行文字 全绿")
|
||||
quit(0)
|
||||
else:
|
||||
for p in _problems:
|
||||
push_error("[battle_log_zero_text_test] FAIL — " + p)
|
||||
print("[battle_log_zero_text_test] FAIL — " + p)
|
||||
quit(1)
|
||||
@@ -15,11 +15,18 @@ var _problems: Array = []
|
||||
# 记参假站桩(d12 排播半):继承真类以过类型面,只记解析入参
|
||||
class FakeStage extends BattleStageView:
|
||||
var calls: Array[Array] = []
|
||||
var fx_rows: Array[Dictionary] = [] # on_step_row_fx 收到的行(FX 链计数载体)
|
||||
|
||||
func on_step_row_fx(row: Dictionary) -> void:
|
||||
fx_rows.append(row.duplicate(true))
|
||||
|
||||
func enemy_standee_center(target_id: String, display_name: String) -> Variant:
|
||||
calls.append([target_id, display_name])
|
||||
return Vector2(10, 10)
|
||||
|
||||
func hero_standee_center() -> Vector2:
|
||||
return Vector2(10, 10)
|
||||
|
||||
|
||||
# 记参假演出层(同上——play_* 全部只记不演)
|
||||
class FakeFx extends SettlementFxView:
|
||||
@@ -105,19 +112,12 @@ func _init() -> void:
|
||||
main.ui.screens.row_pacer.flush()
|
||||
var snap_after: String = JSON.stringify(battle.diagnostic_snapshot())
|
||||
check(snap_before == snap_after, "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)")
|
||||
# ---- 断言②:零丢行(结构判据:排空后队列恒空 + 末信号行原文在带——append_row 直落不吞行)----
|
||||
# ---- 断言②:零丢行(逐行文字列表退役后改判〔退役改 V-退役卡 R2〕:结构判据=排空后队列恒空,
|
||||
# 行的落地面由「原文在明细带」改为「逐行进 FX 链」——由下方 _assert_fx_chain 对全行集断言承接)----
|
||||
check(rows_seen.size() > 0, "结算行已产生(断言前提)")
|
||||
check(main.ui.screens.row_pacer.pending_rows().is_empty(), "排空后队列恒空(flush 完整性)")
|
||||
var last_text := ""
|
||||
for i in range(rows_seen.size() - 1, -1, -1):
|
||||
var cand_row: Dictionary = rows_seen[i]
|
||||
last_text = String(cand_row.get("text", ""))
|
||||
if last_text == "":
|
||||
last_text = String(cand_row.get("display_name", ""))
|
||||
if last_text != "":
|
||||
break
|
||||
check(last_text != "" and main.ui.battle.log_view.parsed_text().contains(last_text),
|
||||
"末信号行原文在明细带(排空不吞行:『%s』)" % last_text)
|
||||
# ---- 断言②改判承接:FX 链逐行收拍(真实行集驱动——每行一拍站桩反馈,弹道/标出按型计数逐位对上)----
|
||||
_assert_fx_chain(main, rows_seen)
|
||||
# ---- 断言③:段间延迟取数面双态 ----
|
||||
UiKit.motion_scale = 1.0
|
||||
var d_on: float = main.pace_delay_for("damage", "heal")
|
||||
@@ -156,6 +156,110 @@ func _count_row(row: Dictionary, arr: Array) -> void:
|
||||
arr.append(row.duplicate(true))
|
||||
|
||||
|
||||
# FX 排播链断言(逐行文字列表退役后的行落地面——〔退役改 V-退役卡 R2〕):
|
||||
# 判据出处: design/ux/battle.md:439 动效:结算逐行兑现为信息载体不可整体跳过,可加倍(减动效只调时长)
|
||||
# 判据出处: design/gdd/08-Alpha版本标准.md:154 战斗演出链(弹道 / 受击 / 血条 / 灰化)在役
|
||||
# 真实行集(夹具采样的 step_row 载荷)驱动独立 RowPacer,断言:
|
||||
# ① 每行恰好一拍站桩反馈(on_step_row_fx 计数=行数);② 弹道/标出调用计数与行型分布逐位对上
|
||||
# (期望从行集独立推导、实际从记参假件读——不镜像实现);③ 断言前提=行集非空(非空自检)。
|
||||
# 记参假棋盘 FX(highlight 分支载体——perfect sweep / seal hit 弹道只记不演)
|
||||
class FakeBoardFx extends BoardFxView:
|
||||
var calls: Array[String] = []
|
||||
|
||||
func play_perfect_sweep() -> void:
|
||||
calls.append("perfect_sweep")
|
||||
|
||||
func play_seal_hit_projectile(_heavy: bool) -> void:
|
||||
calls.append("seal_hit_projectile")
|
||||
|
||||
|
||||
# 记参假 toast(skill_unlock 行载体)
|
||||
class FakeToast extends ToastStack:
|
||||
var texts: Array[String] = []
|
||||
|
||||
func show_toast(text: String, _tone: String = "info") -> void:
|
||||
texts.append(text)
|
||||
|
||||
|
||||
# 记参假棋盘区(_consume_row highlight 分支的 fx/toast 载体)
|
||||
class FakeBoardZone extends BoardZoneView:
|
||||
var board_fx := FakeBoardFx.new()
|
||||
var toast_stack := FakeToast.new()
|
||||
|
||||
|
||||
func _assert_fx_chain(main: Node, rows_seen: Array) -> void:
|
||||
var rp = RowPacerScript.new()
|
||||
var fstage := FakeStage.new(null, root.get_node("/root/ConfigDB").presentation.battle_layout)
|
||||
var ffx := FakeFx.new()
|
||||
var fzone := FakeBoardZone.new(root.get_node("/root/ConfigDB").presentation.battle_layout)
|
||||
fzone.fx = fzone.board_fx
|
||||
fzone.toast = fzone.toast_stack
|
||||
rp.stage = fstage
|
||||
rp.board_view = main.ui.battle.board_zone.board_view
|
||||
rp.settlement_fx = ffx
|
||||
rp.board_zone = fzone
|
||||
rp.refresh_request = func() -> void: pass
|
||||
rp.pump_ended = func() -> void: pass
|
||||
for row in rows_seen:
|
||||
rp.enqueue(row.duplicate(true))
|
||||
rp.flush()
|
||||
check(rows_seen.size() > 0, "FX 链断言前提:行集非空(非空自检)")
|
||||
check(fstage.fx_rows.size() == rows_seen.size(),
|
||||
"每行恰好一拍站桩反馈(on_step_row_fx %d/%d)〔退役改 V-退役卡 R2〕" % [fstage.fx_rows.size(), rows_seen.size()])
|
||||
# 期望计数从行集独立推导(演出语义=0.4 硬契约:伤害行弹道+数字、治疗行弹道+数字、效果行色环)
|
||||
var exp_projectile := 0
|
||||
var exp_mark := 0
|
||||
var exp_pulse := 0
|
||||
for row in rows_seen:
|
||||
var t := String(row.get("type", ""))
|
||||
var tgt := String(row.get("target", ""))
|
||||
if t == "damage":
|
||||
exp_projectile += 1
|
||||
exp_mark += 1
|
||||
elif t == "heal" and (tgt == "hero" or tgt == ""):
|
||||
exp_projectile += 1
|
||||
exp_mark += 1
|
||||
elif t == "heal":
|
||||
exp_mark += 1
|
||||
elif t == "effect":
|
||||
exp_pulse += 1
|
||||
var got_projectile := 0
|
||||
var got_mark := 0
|
||||
var got_pulse := 0
|
||||
for c in ffx.calls:
|
||||
match String(c[0]):
|
||||
"projectile": got_projectile += 1
|
||||
"mark": got_mark += 1
|
||||
"pulse": got_pulse += 1
|
||||
check(got_projectile == exp_projectile,
|
||||
"跨区弹道计数与行型分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [got_projectile, exp_projectile])
|
||||
check(got_mark == exp_mark,
|
||||
"结算标出数字计数与行型分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [got_mark, exp_mark])
|
||||
check(got_pulse == exp_pulse,
|
||||
"状态色环计数与行型分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [got_pulse, exp_pulse])
|
||||
# highlight 三路(完美封印横幅=高光行非解锁技;封印命中加重弹道=seal_hit 高亮键不限行型;
|
||||
# Boss 解锁技名 toast=skill_unlock 键)——期望按行协议语义独立推导
|
||||
var exp_sweep := 0
|
||||
var exp_seal_hit := 0
|
||||
var exp_toast := 0
|
||||
for row in rows_seen:
|
||||
var t := String(row.get("type", ""))
|
||||
var hl := String(row.get("highlight", ""))
|
||||
if t == "highlight" and hl != "skill_unlock":
|
||||
exp_sweep += 1
|
||||
elif hl == "seal_hit":
|
||||
exp_seal_hit += 1
|
||||
if hl == "skill_unlock":
|
||||
exp_toast += 1
|
||||
check(fzone.board_fx.calls.count("perfect_sweep") == exp_sweep,
|
||||
"完美封印横幅计数与行分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [fzone.board_fx.calls.count("perfect_sweep"), exp_sweep])
|
||||
check(fzone.board_fx.calls.count("seal_hit_projectile") == exp_seal_hit,
|
||||
"封印命中弹道计数与行分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [fzone.board_fx.calls.count("seal_hit_projectile"), exp_seal_hit])
|
||||
check(fzone.toast_stack.texts.size() == exp_toast,
|
||||
"解锁技名 toast 计数与行分布逐位一致(%d/%d)〔退役改 V-退役卡 R2〕" % [fzone.toast_stack.texts.size(), exp_toast])
|
||||
rp.free()
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
|
||||
for p in ["user://test_pacing_queue_save.json", "user://test_pacing_queue_save.json.tmp"]:
|
||||
|
||||
@@ -101,11 +101,17 @@ func _sweep_source_names() -> void:
|
||||
row_view._hide_tip()
|
||||
Kit.check("结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕",
|
||||
String(_db.combat.settlement.core.source_label_map.get("mine_hit", "")) == "危险格")
|
||||
var log_view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout)
|
||||
var row_txt := String(log_view._format_row({"type": "effect", "source_class": "mine_hit",
|
||||
"display_name": "挖掘反噬", "value": -5, "pool_value": -5, "round_index": 1}))
|
||||
Kit.check("战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕",
|
||||
row_txt.contains("(危险格)"))
|
||||
# u6 消费半退役改写〔退役改 V-退役卡 R1——test-conventions §5.5 同生死〕:逐行文字列表退役后
|
||||
# 行格式化域(_format_row)摘除,来源标签玩家可见面只余状态槽 tooltip(u5 承接)与数据表;
|
||||
# 断言改为退役态防回潮——行格式化方法不在位(防旧行文本呈现路径复活)。
|
||||
var LogViewScript: Script = load("res://presentation/views/battle_log_view.gd")
|
||||
var has_fmt := false
|
||||
for m in LogViewScript.get_script_method_list():
|
||||
if String(m.get("name", "")) == "_format_row":
|
||||
has_fmt = true
|
||||
break
|
||||
Kit.check("行格式化域已退役(_format_row 不在位——来源标签只由数据表与 tooltip 承载)〔wa7〕",
|
||||
not has_fmt)
|
||||
|
||||
|
||||
# ⑦⑧ 已开危险格字形族(u7/u8/u9——BoardView rebuild 真实刷新路径+源 token 扫描)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 日志 step 行零文案与全字段落盘断言(结算期文字列表退役批 T2——日志零文案只存字段):
|
||||
# 真实入口 = sim 采集链(LevelAutoplay.run_level 挂 PlaytestLogger.start_session → 真实结算 →
|
||||
# close_session 落盘 JSONL 读回),断言:
|
||||
# ① 全型 step 行零中文文案(text / display_name 两键不存在);
|
||||
# ② 公共七字段(kind/type/source/cell/round/value/highlight)与平铺原件(origin_cell/round_index/
|
||||
# source_class)逐型在位——日志逐行全字段落盘义务不变(公共字段集除文案两键外零删减);
|
||||
# ③ payload 键抽样在位(damage 行 roll/ac/tier/normal/pierce/magic/baseline——改前键集实证在册,
|
||||
# 反事实精确重算数据面不被零文案改动静默吞键)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/playtest_logger_step_fields_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const ConfigDBScript := preload("res://core/config_db.gd")
|
||||
|
||||
var _problems: Array = []
|
||||
|
||||
|
||||
func check(cond: bool, msg: String) -> void:
|
||||
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
|
||||
if not cond:
|
||||
_problems.append(msg)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# 判据出处: design/gdd/08-Alpha版本标准.md:154 演出链在役(逐行结算排播已随 v1.29 退役)——
|
||||
# 日志零文案只存字段(text/display_name 不落盘),其余全字段与 payload 逐键保留
|
||||
var db = ConfigDBScript.new()
|
||||
if not db.load_tables("res://data/tables", true):
|
||||
_problems.append("数值表加载失败(夹具前提)")
|
||||
_finish()
|
||||
return
|
||||
_assert_no_text_and_full_payload(db)
|
||||
_finish()
|
||||
|
||||
|
||||
func _assert_no_text_and_full_payload(db) -> void:
|
||||
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:87 显示名等玩家可见文案是数据面字段(日志消费端零文案)
|
||||
var logger = load("res://presentation/playtest_logger.gd").new()
|
||||
logger.ap_per_round = int(db.run.action_economy.ap_per_round) # 注入序前置(守卫拒后开——R5 必填)
|
||||
var log_path: String = logger.start_session(4242)
|
||||
var autoplay = load("res://sim/level_autoplay.gd")
|
||||
var st = autoplay.run_level(db, 4242, 1, 0, 0, false, logger, "normal", "min")
|
||||
logger.close_session()
|
||||
check(bool(st.get("terminated", false)) or int(st.get("rounds", 0)) > 0,
|
||||
"真实结算已跑(夹具前提:rounds>0——result=%s log_path=%s)" % [String(st.get("result", "?")), String(st.get("log_path", "?"))])
|
||||
# 落盘 JSONL 读回(start_session 返回路径——采集链同源)
|
||||
var f := FileAccess.open(log_path, FileAccess.READ)
|
||||
check(f != null, "日志文件可读(%s)" % log_path)
|
||||
if f == null:
|
||||
return
|
||||
var steps: Array = []
|
||||
while not f.eof_reached():
|
||||
var line := f.get_line()
|
||||
if line.strip_edges() == "":
|
||||
continue
|
||||
var parsed: Variant = JSON.parse_string(line)
|
||||
if parsed is Dictionary and String(parsed.get("kind", "")) == "step":
|
||||
steps.append(parsed)
|
||||
f.close()
|
||||
check(steps.size() > 0, "step 行非空(断言前提:%d 行)" % steps.size())
|
||||
# ---- ① 全型零文案(text / display_name 两键不存在)----
|
||||
var text_hits := 0
|
||||
var display_hits := 0
|
||||
for s in steps:
|
||||
if (s as Dictionary).has("text"):
|
||||
text_hits += 1
|
||||
if (s as Dictionary).has("display_name"):
|
||||
display_hits += 1
|
||||
check(text_hits == 0, "全 step 行零 text 键(命中 %d/%d)〔退役改 V-退役卡 R3〕" % [text_hits, steps.size()])
|
||||
check(display_hits == 0, "全 step 行零 display_name 键(命中 %d/%d)〔退役改 V-退役卡 R3〕" % [display_hits, steps.size()])
|
||||
# ---- ② 公共七字段 + 平铺原件逐型在位 ----
|
||||
var common := ["kind", "type", "source", "cell", "round", "value", "highlight",
|
||||
"origin_cell", "round_index", "source_class"]
|
||||
var misses: Array[String] = []
|
||||
for s in steps:
|
||||
for k in common:
|
||||
if not (s as Dictionary).has(k):
|
||||
misses.append("%s 缺 %s" % [String(s.get("type", "?")), k])
|
||||
check(misses.is_empty(), "公共字段与平铺原件逐行在位(缺:%s)〔退役改 V-退役卡 R3〕" % [", ".join(misses)])
|
||||
# ---- ③ damage 行 payload 键并集抽样(反事实精确重算数据面在册——单行键随行型分布,
|
||||
# 并集才是改前契约形态:掷点键逐行必有、分量键按攻击来源分布)----
|
||||
var dmg_union := {}
|
||||
var dmg_seen := 0
|
||||
for s in steps:
|
||||
if String(s.get("type", "")) == "damage":
|
||||
dmg_seen += 1
|
||||
for k in (s as Dictionary).keys():
|
||||
dmg_union[k] = true
|
||||
check(dmg_seen > 0, "damage 行在册(抽样前提:%d 行)" % dmg_seen)
|
||||
var payload_missing: Array[String] = []
|
||||
for k in ["roll", "ac", "tier", "normal", "baseline"]:
|
||||
if not dmg_union.has(k):
|
||||
payload_missing.append(k)
|
||||
check(payload_missing.is_empty(),
|
||||
"damage 行 payload 键并集在位(缺:%s)〔退役改 V-退役卡 R3〕" % [", ".join(payload_missing)])
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
# 判据出处: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组)
|
||||
if _problems.is_empty():
|
||||
print("[playtest_logger_step_fields_test] PASS — 日志 step 行零文案与全字段落盘 全绿")
|
||||
quit(0)
|
||||
else:
|
||||
for p in _problems:
|
||||
push_error("[playtest_logger_step_fields_test] FAIL — " + p)
|
||||
print("[playtest_logger_step_fields_test] FAIL — " + p)
|
||||
quit(1)
|
||||
@@ -378,51 +378,92 @@ func _smoke_type_glow() -> void:
|
||||
viewB.free()
|
||||
|
||||
|
||||
# ---- 战斗日志视图·行格式化呈现层同步闸(真人试玩卡断 bug 回归:
|
||||
# 伤害行 attach 字段变更后本视图拆行分支漏网未同步——敌方首笔攻击行带 baseline 无 attach,
|
||||
# _format_row Invalid access 使结算段中途夭折、界面卡死。行协议变更必须同步呈现层〔战斗结算 §行协议〕,
|
||||
# 本段把现行行形态逐型喂进真实视图:拆行残留与行字段错位在此转红)----
|
||||
# ---- 行消费 FX 链呈现层同步闸(原「行格式化 9 类同步闸」退役改写〔退役改 V-退役卡 R1〕:
|
||||
# 逐行文字列表退役后行格式化域摘除,行协议的呈现层消费面 = row_pacer FX 链——原防
|
||||
# 「格式化 Invalid access 卡死结算段」的盲区检测法(解析文本增长)等价迁移为 FX 落拍计数增长:
|
||||
# 行协议 9 类逐型经排播泵消费零炸、每型落拍一拍站桩反馈)----
|
||||
func _smoke_battle_log_view() -> void:
|
||||
# 判据出处: design/gdd/systems/表现与支撑/HUD框架.md:272 (明细带行协议组)
|
||||
print("--- 战斗日志视图行格式化(行协议 9 类呈现层同步闸)---")
|
||||
var view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout)
|
||||
# 敌方行动伤害行(现行形态:baseline 在场、attach 已出协议——修复前此行即 Invalid access)
|
||||
view.append_row({"type": "damage", "display_name": "地精 撕咬", "value": 2, "target": "主角",
|
||||
"baseline": 3, "roll": 15, "ac": 12, "tier": 100,
|
||||
"source_class": "enemy_action", "round_index": 2})
|
||||
var enemy_txt: String = view.parsed_text()
|
||||
Kit.check("敌方伤害行格式化(baseline 行不读退役 attach)",
|
||||
enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12"))
|
||||
Kit.check("退役伏击/基线拆行文案不再出现",
|
||||
not enemy_txt.contains("伏击") and not enemy_txt.contains("基线"))
|
||||
# 主角攻击行·单分量模型(payload 只带实际攻击类型一键——pierce/magic 多键即崩)
|
||||
view.append_row({"type": "damage", "display_name": "主角攻击", "value": 4, "pierce": 4,
|
||||
"roll": 20, "ac": 13, "tier": 100, "crit": true, "killed": true,
|
||||
"source_class": "hero_attack", "round_index": 2})
|
||||
var hero_txt: String = view.parsed_text()
|
||||
Kit.check("主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)",
|
||||
hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■"))
|
||||
Kit.check("退役三分量拆行文案不再出现", not hero_txt.contains(" + 穿刺 "))
|
||||
# 未生效行聚合(pattern 3 聚合纪律——同键两行呈现 ×2 不逐行刷屏)
|
||||
view.append_row({"type": "reward_ineffective", "display_name": "增益未生效(满层)", "batch_key": "attack_up", "source_class": "safe_cell", "round_index": 1})
|
||||
view.append_row({"type": "reward_ineffective", "display_name": "增益未生效(满层)", "batch_key": "attack_up", "source_class": "safe_cell", "round_index": 1})
|
||||
view.append_row({"type": "heal", "display_name": "急救包", "value": 3, "target": "主角", "source_class": "safe_cell", "round_index": 1})
|
||||
var agg_txt: String = view.parsed_text()
|
||||
Kit.check("未生效行聚合计数 ×2 呈现", agg_txt.contains("⚠") and agg_txt.contains("×2"))
|
||||
# 其余行型逐型喂入:格式化中途夭折 = 解析文本零增长(脚本错误静默吞断言的盲区由此转为可见 FAIL)
|
||||
var rest := {
|
||||
# 判据出处: design/gdd/08-Alpha版本标准.md:154 战斗演出链(弹道 / 受击 / 血条 / 灰化)在役——
|
||||
# 行协议 9 类逐型经排播泵消费零炸、每型落拍(明细带行协议呈现已退役)
|
||||
print("--- 行消费 FX 链同步闸(行协议 9 类逐型零炸)---")
|
||||
var rp = load("res://presentation/row_pacer.gd").new()
|
||||
var fstage := _FxChainFakeStage.new(null, _db.presentation.battle_layout)
|
||||
var ffx := _FxChainFakeFx.new()
|
||||
var fzone := _FxChainFakeZone.new(_db.presentation.battle_layout)
|
||||
fzone.fx = fzone.board_fx
|
||||
fzone.toast = fzone.toast_stack
|
||||
rp.stage = fstage
|
||||
rp.board_view = BoardView.new()
|
||||
rp.settlement_fx = ffx
|
||||
rp.board_zone = fzone
|
||||
rp.refresh_request = func() -> void: pass
|
||||
rp.pump_ended = func() -> void: pass
|
||||
# 行协议 9 类逐型喂入(现行形态样本——敌方伤害行 baseline 在场 attach 已出协议;
|
||||
# 格式化中途夭折 = 落拍零增长,脚本错误静默吞断言的盲区由此转为可见 FAIL)
|
||||
var samples := {
|
||||
"damage": {"type": "damage", "display_name": "地精 撕咬", "value": 2, "target": "主角",
|
||||
"baseline": 3, "roll": 15, "ac": 12, "tier": 100,
|
||||
"source_class": "enemy_action", "round_index": 2},
|
||||
"effect": {"type": "effect", "display_name": "攻击强化", "value": 1, "pool_value": 2, "effect_id": "attack_up", "source_class": "seal_hit", "round_index": 1},
|
||||
"heal": {"type": "heal", "display_name": "急救包", "value": 3, "target": "主角", "source_class": "safe_cell", "round_index": 1},
|
||||
"reward_ineffective": {"type": "reward_ineffective", "display_name": "增益未生效(满层)", "batch_key": "attack_up", "source_class": "safe_cell", "round_index": 1},
|
||||
"seal_miss": {"type": "seal_miss", "display_name": "封印未中", "source_class": "seal_miss", "round_index": 1},
|
||||
"cast": {"type": "cast", "display_name": "卓尔精灵 施放 毒雾", "source_class": "enemy_action", "round_index": 3},
|
||||
"decay": {"type": "decay", "value": 1, "round_index": 3},
|
||||
"verdict": {"type": "verdict", "display_name": "下一轮次", "value": 0, "round_index": 3},
|
||||
"highlight": {"type": "highlight", "display_name": "完美封印!清图奖励 ×2", "value": 2, "source_class": "clear_reward", "round_index": 4},
|
||||
}
|
||||
for row_type in rest:
|
||||
var before: int = view.parsed_text().length()
|
||||
view.append_row(rest[row_type])
|
||||
Kit.check("行型 %s 格式化落地(解析文本增长)" % String(row_type),
|
||||
view.parsed_text().length() > before)
|
||||
for row_type in samples:
|
||||
var before: int = fstage.fx_rows.size()
|
||||
rp.enqueue(samples[row_type])
|
||||
rp.flush() # rp 未进树——同步排空消费(pacing_queue_test 同款)
|
||||
Kit.check("行型 %s FX 落拍(站桩反馈计数增长)" % String(row_type),
|
||||
fstage.fx_rows.size() > before)
|
||||
Kit.check("逐型全落拍(9 类行协议消费零炸)", fstage.fx_rows.size() == samples.size())
|
||||
rp.free()
|
||||
|
||||
|
||||
# FX 链逐型闸记参假件(pacing_queue_test 同款形态——本套件独立定义防跨套件耦合)
|
||||
class _FxChainFakeStage extends BattleStageView:
|
||||
var fx_rows: Array[Dictionary] = []
|
||||
|
||||
func on_step_row_fx(row: Dictionary) -> void:
|
||||
fx_rows.append(row.duplicate(true))
|
||||
|
||||
func enemy_standee_center(_target_id: String, _display_name: String) -> Variant:
|
||||
return Vector2(10, 10)
|
||||
|
||||
func hero_standee_center() -> Vector2:
|
||||
return Vector2(10, 10)
|
||||
|
||||
|
||||
class _FxChainFakeFx extends SettlementFxView:
|
||||
func play_projectile(_from: Vector2, _to: Vector2, _glyph: String, _col: Color, _crit: bool = false) -> void:
|
||||
pass
|
||||
|
||||
func play_damage_number(_pos: Vector2, _text: String, _col: Color, _crit: bool = false) -> void:
|
||||
pass
|
||||
|
||||
func play_status_pulse(_pos: Vector2, _col: Color) -> void:
|
||||
pass
|
||||
|
||||
|
||||
class _FxChainFakeZone extends BoardZoneView:
|
||||
var board_fx := _FxChainFakeBoardFx.new()
|
||||
var toast_stack := _FxChainFakeToast.new()
|
||||
|
||||
|
||||
class _FxChainFakeBoardFx extends BoardFxView:
|
||||
func play_perfect_sweep() -> void:
|
||||
pass
|
||||
|
||||
func play_seal_hit_projectile(_heavy: bool) -> void:
|
||||
pass
|
||||
|
||||
|
||||
class _FxChainFakeToast extends ToastStack:
|
||||
func show_toast(_text: String, _tone: String = "info") -> void:
|
||||
pass
|
||||
|
||||
|
||||
# ---- 开场行后缀尾接协议(迭代36 wc1 静态半——level_open_line 第五参布尔直控尾接,签名零变;
|
||||
|
||||
Reference in new issue
Block a user