Merge branch 'master' into iter43

This commit is contained in:
DKChan committed 2026-10-29 23:35:12 +08:00
commit d7b8839fcf
38 files changed
+1328 -105

No files matched your search

+1
View File
@@ -129,6 +129,7 @@ tests/
| 中文字体随包与字形覆盖 | HUD框架 | `UiKit.load_font()` | `ui_font_asset_test.gd:_init`;`theme_projection_test.gd:_smoke_global_theme` |
| 地形瓦片套与格面数字可读 | HUD框架 | `TilePalette.digit_ink_mode()`/`TilePalette.normalize_floor_luma()` | `terrain_tile_sets_test.gd:_test_set_completeness`;`terrain_tile_sets_test.gd:_test_hue_domains`;`terrain_tile_sets_test.gd:_test_digit_ink_modes` |
| 站桩敌人呈现与结算演出落点 | 战斗表现 | `BattleStageView.enemy_standee_center()`/`BattleEndOverlay.open_victory()` | `standee_fx_sweep_test.gd:_init`;`pacing_queue_test.gd:_init` |
| 调参测速批量模拟(逐帧步进与统计读数) | 调参测量 | `TuneSimController.run()`(调试浮窗批量模拟入口) | `tune_sim_batch_parity_test.gd:_init`(配对对拍套件,随测速循环批建) |
| 战斗叙述与终局文案(数值带入/胜负互异) | HUD框架 | `BattleNarrator.battle_end_text()`/`BattleNarrator.boss_name()` | `battle_narrator_test.gd:_test_numeric_lines_carry_values`;`battle_narrator_test.gd:_test_end_text_differs_by_result`;`battle_narrator_test.gd:_test_boss_name_uses_boss_rowset` |
| 玩家面文案与危险格格面字形 | HUD框架 | `BoardView.rebuild()`/`StatusSlotRow.set_effects()` | `player_copy_sweep_test.gd:_sweep_board_glyph`;`player_copy_sweep_test.gd:_sweep_legend`;`player_copy_sweep_test.gd:_sweep_hero_panel` |
@@ -478,7 +478,7 @@ func _smoke_save_slots() -> void:
fsrc.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 1234}}))
fsrc.close()
DirAccess.remove_absolute(slot_tpl % 1)
ml._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
var migrated: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1))
Kit.check("sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕",
int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234)
@@ -489,7 +489,7 @@ func _smoke_save_slots() -> void:
var fsrc2 := FileAccess.open(legacy_src, FileAccess.WRITE)
fsrc2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 777}}))
fsrc2.close()
ml._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
ml.slots._migrate_legacy_save(legacy_src, slot_tpl % 1, legacy_src + ".migrated.bak")
var slot1_now: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(slot_tpl % 1))
Kit.check("sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕",
int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234
+5 -1
View File
@@ -17,10 +17,14 @@ class StubTable:
return []
class StubDB:
class StubCrossrun:
var treasure_items: StubTable
class StubDB:
var crossrun := StubCrossrun.new()
func _stub_db() -> StubDB:
var t := StubTable.new()
t.rows = {
@@ -52,7 +52,7 @@ func _init() -> void:
var sv = load("res://core/save_manager.gd").new("user://r1_mech_test_save.json")
var mp = load("res://core/meta_progression.gd").new(db, sv)
mt.rows = [base_pre.duplicate(true), base_dep.duplicate(true)]
db.set("meta_tree", mt)
db.crossrun.meta_tree = mt
sv.meta_state.set_tree_level("gate_pre", 1)
if mp.tree_can_buy("gate_dep"):
failures += 1
@@ -23,6 +23,9 @@ func _init() -> void:
func _trigger_oob() -> void:
# 共享 .tres 上的 resolver 归最后装载的实例——autoload ConfigDB 的 _ready 在测试 _init 之后
# 二次注入覆盖私有实例,故触发前把解析通道归还被测实例(诊断落 _db,退出码判据才成立)
_db.combat.board_gen._resolver = _db._resolver
# 越域请求(关卡序号 0 < 合法域 ≥1)——_fatal 内部 push fatal 诊断并 quit(1)(帧末退出)
var got: Dictionary = _db.combat.board_gen.resolve_row(0, 0)
if _db.diagnostics_count("fatal") >= 1:
+10 -10
View File
@@ -137,16 +137,16 @@ func _test_shop_accessors(db) -> void:
var got: Dictionary = db.run.shop.product_row(String(first.product_id))
Kit.check("shop_product_row 按 product_id 精确命中首行", got == first)
Kit.check("shop_product_row 未知 id 返回空字典(容错通道)", db.run.shop.product_row("no_such_product_id__").is_empty())
var tier_rows: Array = db._shop_rows("gp_drops")
var tier_rows: Array = db.run.shop.gp_drops
if not tier_rows.is_empty():
var tier0: String = String(tier_rows[0].tier)
Kit.check("shop_gp_drop_row 按 tier 命中", not db.run.shop.gp_drop_row(tier0).is_empty())
var bonus_rows: Array = db._shop_rows("level_gp_bonus")
var bonus_rows: Array = db.run.shop.level_gp_bonus
if not bonus_rows.is_empty():
var blv: int = int(bonus_rows[0].level_index)
Kit.check("shop_level_gp_bonus 按关卡序号命中行值", db.run.shop.level_gp_bonus_at(blv) == int(bonus_rows[0].bonus))
Kit.check("shop_level_gp_bonus 未登记关卡返回 0(兜底通道)", db.run.shop.level_gp_bonus_at(-1) == 0)
var price_rows: Array = db._shop_rows("price_level_bonus")
var price_rows: Array = db.run.shop.price_level_bonus
if not price_rows.is_empty():
var plv: int = int(price_rows[0].level_index)
Kit.check("shop_price_level_bonus 按关卡序号命中行值", db.run.shop.price_level_bonus_at(plv) == int(price_rows[0].bonus))
@@ -156,19 +156,19 @@ func _test_shop_accessors(db) -> void:
func _test_meta_accessors(db) -> void:
# 无 forge_products 行族断言(行族/访问器不在表,V20 清单)
var key_rows: Array = db._meta_rows("boss_keystone")
var key_rows: Array = db.crossrun.meta.boss_keystone
if not key_rows.is_empty():
var klv: int = int(key_rows[0].level_index)
Kit.check("meta_boss_keystone_row 按关卡序号命中", db.crossrun.meta.boss_keystone_row(klv) == key_rows[0])
var conv_rows: Array = db._meta_rows("conversion")
var conv_rows: Array = db.crossrun.meta.conversion
if not conv_rows.is_empty():
var cattr: String = String(conv_rows[0].attr)
var conv_hit := {}
for r in db.crossrun.meta.conversion:
if String(r.get("attr", "")) == cattr:
conv_hit = r
break
Kit.check("meta.conversion 行集按 attr 命中(访问器退役改直扫同判据)", conv_hit == conv_rows[0])
for r in db.crossrun.meta.conversion:
if String(r.get("attr", "")) == cattr:
conv_hit = r
break
Kit.check("meta.conversion 行集按 attr 命中(访问器退役改直扫同判据)", conv_hit == conv_rows[0])
# ---- TABLE_MANIFEST ↔ data/tables 目录同步(各系统→D2 单点契约的登记面)----
@@ -515,10 +515,15 @@ func _smoke_meta() -> void:
Kit.check("boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)", boss_ok)
class _MetaStateMock:
# 最小元进度件形态——attunement.slots 经 meta_state.tree_state() 读已购等级
func tree_state() -> Dictionary:
return {"nodes": {"att_socket1": 1}, "fork_sides": {}}
class _TreeSaveMock:
# 核代码增补:最小 save 形态——attunement.slots 只读 tree_level("att_socketN")
func tree_level(n: String) -> int:
return 1 if n == "att_socket1" else 0
# 最小 save 形态——attunement.slots 只读 save.meta_state.tree_state().nodes["att_socketN"]
var meta_state := _MetaStateMock.new()
func _conversion_row_of(rows: Array, attr: String) -> Dictionary:
@@ -15,7 +15,7 @@ func _initialize() -> void:
func _row_by_id(ov) -> Dictionary:
var out := {}
for r in ov._forge_rows:
for r in ov._forge._forge_rows:
out[String(r.route_id)] = r
return out
@@ -42,16 +42,16 @@ func _run() -> void:
# ① 锁定态:树钥匙未点亮 → 工作间不可见、两区零行板(无任何可交互购买路径)
ov.open_camp({"balance": 999, "forge": mp.forge_shop_status(), "tree": []})
Kit.check("锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕",
not ov._forge_workbench.visible and ov._forge_lock_l.visible)
not ov._forge._forge_workbench.visible and ov._forge._forge_lock_l.visible)
Kit.check("锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕",
ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0)
ov._forge._forge_zone_weapon.get_child_count() == 0 and ov._forge._forge_zone_armor.get_child_count() == 0)
# ② 解锁态:钥匙点亮 → 两区各三路线
ov.open_camp({"balance": 500, "forge": mp.forge_shop_status(),
"tree": [{"node_id": "forge_workshop", "level": 1}]})
Kit.check("解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕", ov._forge_workbench.visible)
Kit.check("解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕", ov._forge._forge_workbench.visible)
Kit.check("两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕",
ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3)
ov._forge._forge_zone_weapon.get_child_count() == 3 and ov._forge._forge_zone_armor.get_child_count() == 3)
var rows: Dictionary = _row_by_id(ov)
Kit.check("六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕",
rows.has("sword") and rows.has("spear") and rows.has("staff")
@@ -55,6 +55,19 @@ class FakeDriver extends RefCounted:
"defeat_node_index": 3, "defeat_node_type": "battle", "hp_left": 0.0, "hp_at_boss": 0.0}
# 假配置库(TuneSimController 只读 db.run.action_economy.ap_per_round——最小域对象图)
class FakeActionEconomy:
var ap_per_round := 2
class FakeRun:
var action_economy := FakeActionEconomy.new()
class FakeDB:
var run := FakeRun.new()
func _init() -> void:
UiKit.motion_scale = 1.0
@@ -126,7 +139,7 @@ func _init() -> void:
var panel := FakePanel.new()
root.add_child(panel)
var drv := FakeDriver.new()
var ctrl = TuneSimScript.new({"action_economy": {"ap_per_round": 2}}, panel, 100, drv)
var ctrl = TuneSimScript.new(FakeDB.new(), panel, 100, drv)
drv.ctrl = ctrl
await ctrl.run(2) # 假 driver 回调内读 is_running()(两局各一读)
Kit.check("控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕", drv.seen == [true, true])
@@ -802,7 +802,7 @@ func _camp_recent_projection() -> void:
var camp = load("res://presentation/views/camp_overlay_view.gd").new()
camp.open_camp({"balance": 0, "recent": [
{"result": "completed", "levels_cleared": 2, "keystone_total": 5, "master_seed": 42}]})
var recent_text: String = String(camp._recent_list.text)
var recent_text: String = String(camp._side._recent_list.text)
Kit.check("营地最近冒险行完成关数措辞(实际「%s」)〔口径换代 V6〕" % recent_text,
recent_text.contains("完成 2 关") and not recent_text.contains("通关"))
camp.visible = false