From 80ac80ef1a310fbc685b1e0f380b7dc3a84ff14d Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 17 Oct 2026 10:18:35 +0800 Subject: [PATCH] =?UTF-8?q?test(=E6=96=AD=E8=A8=80=E4=B8=89=E4=BB=B6):=20?= =?UTF-8?q?=E6=9C=AA=E5=BC=80=E6=A0=BC=E7=B1=BB=E5=9E=8B=E5=BE=AE=E5=85=89?= =?UTF-8?q?=E6=9C=BA=E6=A3=80=E9=9D=A2=EF=BC=88=E8=BF=AD=E4=BB=A3=2030=20?= =?UTF-8?q?=E5=8D=A1=E2=91=A0=20T2-=E2=91=A0=E2=91=A1=E2=91=A2=EF=BC=89?= =?UTF-8?q?=E2=80=94=E2=80=94=E2=91=A0=E5=86=92=E7=83=9F=20smoke=5Fall.gd?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=5Fsmoke=5Ftype=5Fglow=20=E5=85=AB?= =?UTF-8?q?=E7=9B=B8=2024=20=E9=A1=B9=EF=BC=88=E7=9B=B80=20=E4=BD=9C?= =?UTF-8?q?=E7=94=A8=E9=9B=86=E6=81=92=E7=AD=89=EF=BC=8B=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=BC=80=EF=BC=8B=E5=B7=B2=E5=BC=80/r0=20=E9=9B=B6=E5=BE=AE?= =?UTF-8?q?=E5=85=89=EF=BC=8F=E7=9B=B81=20=E5=85=AB=E9=82=BB=E6=B4=BE?= =?UTF-8?q?=E5=85=AB=E5=9E=8B=E4=BA=94=E7=BB=84=E9=80=90=E6=A0=BC=E6=98=A0?= =?UTF-8?q?=E5=B0=84=EF=BC=8F=E7=9B=B82=20=E6=B8=85=E5=8D=95=E5=A4=96"keys?= =?UTF-8?q?tone"=E6=98=BE=E5=BC=8F=E7=99=BB=E8=AE=B0=EF=BC=8B=E9=9B=B6?= =?UTF-8?q?=E5=BE=AE=E5=85=89=EF=BC=8F=E7=9B=B83=20=E5=B0=81=E9=94=81?= =?UTF-8?q?=E6=A0=BC=E9=9B=B6=E5=BE=AE=E5=85=89=E9=9B=B6=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=EF=BC=8F=E7=9B=B84=20=E8=A7=A3=E5=B0=81=E5=A4=8D=E4=BD=8D?= =?UTF-8?q?=EF=BC=8F=E7=9B=B85=20off/on=20=E4=B8=A4=E6=80=81=EF=BC=8B?= =?UTF-8?q?=E5=85=B3=E5=89=8D=E5=BF=AB=E7=85=A7=E9=80=90=E4=BD=8D=E5=A4=8D?= =?UTF-8?q?=E7=8E=B0=EF=BC=8B=E9=9B=B6=E7=8E=A9=E6=B3=95=E5=86=99=E5=85=A5?= =?UTF-8?q?=E4=B8=89=E9=9D=A2=EF=BC=8F=E7=9B=B86=20=E6=8D=A2=E5=9B=BE=20re?= =?UTF-8?q?build=20=E9=87=8D=E7=AE=97=EF=BC=8F=E7=9B=B87=20=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=201=20=E5=90=8C=E6=9D=BF=E5=90=8C=E5=BA=8F=E5=88=97?= =?UTF-8?q?=20on/off=20=E5=8F=8C=E5=AE=9E=E4=BE=8B=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E9=80=90=E4=BD=8D=E4=B8=80=E8=87=B4=2012=20=E6=AD=A5=20digest?= =?UTF-8?q?=20=E5=AF=B9=E7=85=A7=EF=BC=89=EF=BC=9B=E5=A4=B9=E5=85=B7=20=3D?= =?UTF-8?q?=20seed=204242=20L1=20=E6=9D=BF=EF=BC=8Bnovice=20=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=EF=BC=88=E5=88=9D=E5=A7=8B=E5=B1=95=E5=BC=80=E8=A6=86?= =?UTF-8?q?=E7=9B=96=208=20=E9=82=BB=E6=95=85=E5=BF=85=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=EF=BC=89=EF=BC=8B=E5=85=AB=E9=82=BB=E6=B4=BE=E5=9E=8B=EF=BC=8B?= =?UTF-8?q?=E5=93=88=E5=B8=8C=E5=85=9C=E5=BA=95=E4=B9=9D=E5=9E=8B=E5=85=A8?= =?UTF-8?q?=E9=9B=86=EF=BC=9B=E5=86=92=E7=83=9F=201127=E2=86=921151=20?= =?UTF-8?q?=E9=A1=B9=E3=80=82=E2=91=A1=E5=8D=95=E6=B5=8B=20type=5Fglow=5Ft?= =?UTF-8?q?est.gd=20=E4=BA=94=E6=AE=B5=2051=20=E6=A3=80=E6=9F=A5=EF=BC=88?= =?UTF-8?q?=E5=BD=92=E7=BB=84=E5=B0=81=E9=97=AD=E5=90=AB=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E5=A4=96=206=20=E8=B4=9F=E6=A0=B7=E6=9C=AC=EF=BC=8B=E4=B8=A4?= =?UTF-8?q?=E5=B8=A6=E5=B9=B6=E5=AD=98=E9=94=9A=E5=AE=9A=EF=BC=8F=E5=86=BB?= =?UTF-8?q?=E7=BB=93=E8=89=B2=E7=9B=B8=E9=9D=A2=20HSV=20=E7=A8=BD=E6=A0=B8?= =?UTF-8?q?=20=CE=94H=E2=89=A40.2=C2=B0=C2=B7=CE=94S=E2=89=A40.01=EF=BC=8B?= =?UTF-8?q?=E7=BE=A4=E4=BC=A4=20V=3D0.65=20=E4=B8=93=E9=A1=B9=EF=BC=8F?= =?UTF-8?q?=E5=91=88=E7=8E=B0=E5=8F=82=E6=95=B0=E9=94=9A=E5=AE=9A=20=CE=B1?= =?UTF-8?q?170=C2=B7band=200.125=C2=B7=E7=BB=84=E5=8F=B7=E5=9F=9F=200-5?= =?UTF-8?q?=EF=BC=8F=E4=BC=BD=E9=A9=AC=E5=90=88=E6=88=90=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E9=98=B6=E6=A2=AF=E5=8D=95=E8=B0=83=EF=BC=8B=E7=9B=B8=E9=82=BB?= =?UTF-8?q?=E7=BB=84=E6=A8=A1=E5=9E=8B=20CR=E2=89=A51.20=EF=BC=8Fprobe=20A?= =?UTF-8?q?PI=20=E5=BD=A2=E7=8A=B6=EF=BC=89=EF=BC=8C=E7=99=BB=E8=AE=B0=20r?= =?UTF-8?q?un=5Fall.gd=EF=BC=8833=E2=86=9234=20=E5=A5=97=E4=BB=B6=EF=BC=89?= =?UTF-8?q?=EF=BC=9Bextends=20SceneTree=EF=BC=8BKit/UiKitScript=20preload?= =?UTF-8?q?=EF=BC=88--script=20=E6=A8=A1=E5=BC=8F=E7=A6=81=20class=5Fname?= =?UTF-8?q?=20=E8=A3=B8=E5=BC=95=E7=94=A8=EF=BC=89=E3=80=82=E2=91=A2ui=5Fi?= =?UTF-8?q?nput=5Fprobe=20case=E2=91=B0=20=E6=B4=BB=E6=9D=BF=E9=9D=A2?= =?UTF-8?q?=E8=B5=B0=E6=9F=A5=EF=BC=88=E7=94=9F=E4=BA=A7=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E9=9D=A2=EF=BC=9A=E4=BD=9C=E7=94=A8=E9=9B=86=3D=3Ddiggable=20?= =?UTF-8?q?=E9=94=AE=E9=9B=86=EF=BC=8F=E7=9C=9F=E5=AE=9E=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=8C=96=E5=BC=80=E5=8D=B3=E7=81=AD=EF=BC=8F=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E5=A4=8D=E4=BA=AE=E5=87=BA=E5=9C=88=E7=81=AD=EF=BC=8F=E7=9B=B8?= =?UTF-8?q?=E4=BD=8D=E9=97=A8=E6=8E=A7=E4=B8=A4=E6=80=81=EF=BC=8F=E5=BC=80?= =?UTF-8?q?=E5=85=B3=E4=B8=A4=E6=80=81=E9=9B=B6=E6=89=B0=E5=8A=A8=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E5=8D=81=E5=85=AB=E6=96=AD=E8=A8=80=E5=85=A8=E7=BB=BF?= =?UTF-8?q?=E3=80=82=E6=89=B9=E6=9C=AB=E5=8F=98=E5=BC=82=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E4=BA=94=E7=BB=84=E5=8F=8D=E4=BE=8B=E5=AE=9E=E8=AF=81=EF=BC=9A?= =?UTF-8?q?M1=20=E5=BD=92=E7=BB=84=E9=94=99=E6=98=A0=EF=BC=88=E5=8D=95?= =?UTF-8?q?=E6=B5=8B+=E5=86=92=E7=83=9F=E7=BA=A2=EF=BC=89=EF=BC=8FM2=20?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E5=A4=96=E9=9D=99=E9=BB=98=EF=BC=88=E5=86=92?= =?UTF-8?q?=E7=83=9F=E7=BA=A2=EF=BC=89=EF=BC=8FM3=20=E7=9B=B8=E4=BD=8D?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=8D=E9=87=8D=E7=AE=97=EF=BC=88=E5=86=92?= =?UTF-8?q?=E7=83=9F+=E6=8E=A2=E9=92=88=E7=BA=A2=EF=BC=89=EF=BC=8FM4=20?= =?UTF-8?q?=CE=B1=3D0=20=E4=B8=8D=E7=BB=98=EF=BC=88=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E9=97=B8=E7=BA=A2=EF=BC=89=EF=BC=8FM5=20=E6=B4=BE=E7=94=9F?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E8=AF=AF=E8=A7=A6=E7=8E=A9=E6=B3=95=20API?= =?UTF-8?q?=EF=BC=88=E5=86=92=E7=83=9F=E7=9B=B87=20=E5=88=86=E5=8F=89?= =?UTF-8?q?=E7=BA=A2=EF=BC=89=E2=80=94=E2=80=94=E7=BA=A2=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E4=B8=8E=E8=BF=98=E5=8E=9F=E7=BB=93=E8=AE=BA=E5=AE=9E=E5=BD=95?= =?UTF-8?q?=E8=A7=81=20reports/iter30-=E5=AE=9E=E7=8E=B0=E6=8A=A5=E5=91=8A?= =?UTF-8?q?-2026-10-17.md=20=C2=A7=E5=9B=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/smoke/smoke_all.gd | 268 ++++++++++++++++++ game/smoke/ui_input_probe.gd | 102 ++++++- game/tests/run_all.gd | 1 + .../tests/unit/presentation/type_glow_test.gd | 173 +++++++++++ .../unit/presentation/type_glow_test.gd.uid | 1 + 5 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 game/tests/unit/presentation/type_glow_test.gd create mode 100644 game/tests/unit/presentation/type_glow_test.gd.uid diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index b661d196..826bd31f 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -651,6 +651,7 @@ func _run_core_smoke(db, rng) -> void: _smoke_pool_wiring() _smoke_board_generator() _smoke_layout_cell_formula() + _smoke_type_glow() _smoke_variation_framework() _smoke_variant_placement(1) _smoke_variant_placement(2) @@ -1693,6 +1694,273 @@ func _smoke_layout_cell_formula() -> void: board_view.free() +# ---- 未开格类型微光·数据侧断言(迭代 30 卡① T2-①——V1 作用集恒等/V2 九型归组封闭/V7 零玩法写入)---- +# 夹具纪律:身份回调形状与 reward_economy.query_identity 同构({reward_id, cell_type, tier}); +# 位点覆盖表 _glow_type_overrides(Vector2i → cell_type)+封锁集 _glow_blocked(Vector2i → true, +# 返回 null = 不在摇号清单的封锁格)。哈希兜底按坐标确定性散列——全缸可复现。 +const _GLOW_FIXTURE_TYPES := ["attack", "defense", "blast", "heal", "weaken", + "pierce", "magic", "action", "gp"] +var _glow_type_overrides := {} +var _glow_blocked := {} + + +func _glow_fixture_identity(c: Vector2i) -> Variant: + if _glow_blocked.has(c): + return null + var ctype := String(_glow_type_overrides.get(c, "")) + if ctype == "": + ctype = _GLOW_FIXTURE_TYPES[int(abs(c.x * 31 + c.y * 17)) % _GLOW_FIXTURE_TYPES.size()] + return {"reward_id": "fix_%d_%d" % [c.x, c.y], "cell_type": ctype, "tier": 1} + + +# 英雄 8 邻中未开且未移除的格数(夹具起板前提——初始展开常把 8 邻全揭示) +func _glow_unrevealed_neighbors(ms) -> int: + var n := 0 + var hero: Vector2i = ms.hero_pos() + for c in ms.current_board().neighbors8(hero): + if not ms.is_revealed(c) and not ms.is_removed(c): + n += 1 + return n + + +# 独立重算的期望作用集(不读 BoardView 私有域——恒等断言的两侧各自算) +func _glow_expected_set(b, ms) -> Dictionary: + var out := {} + var hero: Vector2i = ms.hero_pos() + for n in b.neighbors8(hero): + if not ms.is_revealed(n) and not ms.is_removed(n): + out[n] = true + return out + + +# 键集相等(期望集是纯键集——V1 恒等面对照) +func _glow_key_set_equal(a: Dictionary, b: Dictionary) -> bool: + if a.size() != b.size(): + return false + for k in a: + if not b.has(k): + return false + return true + + +# 键值逐位相等(off→on 复现——两侧都是作用集快照,组号亦须逐位一致) +func _glow_map_equal(a: Dictionary, b: Dictionary) -> bool: + if a.size() != b.size(): + return false + for k in a: + if not b.has(k) or int(a[k]) != int(b[k]): + return false + return true + + +func _smoke_type_glow() -> void: + print("--- 未开格类型微光(作用集恒等/九型归组封闭/零玩法写入)---") + var board_generator = load("res://core/board_generator.gd").new() + var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db) + var ae = load("res://core/action_economy.gd").new(_db) + var ms = load("res://core/minesweeper_rules.gd").new(_db, ae) + ms.install_board(b) + ae.notify_round_start() + # 新手策略驱动到「英雄邻域有未开格」态(初始展开常把 8 邻全揭示——取证探针同款起手法; + # 恒定种子确定性,步数上限 40 防冻态空转) + var steps := 0 + while _glow_unrevealed_neighbors(ms) < 5 and steps < 40: + var act: Dictionary = AutoPlayer.novice_action(ms) + if String(act.get("kind", "stop")) == "stop": + break + var op := String(act.get("kind", "")) + if op == "flag": + op = "flag_place" + ms.settle_batch([{"action": op, "cell": act.get("cell")}]) + steps += 1 + var view = load("res://presentation/views/board_view.gd").new() + view.set_identity_resolver(_glow_fixture_identity) + view.rebuild(b, ms) + view.set_dig_glow_enabled(true) + var hero: Vector2i = ms.hero_pos() + # 快照 trio:棋盘态(零玩法写入的对照面)+作用集 + var revealed_before: Array = ms.revealed_cells() + revealed_before.sort() + var hero_before: Vector2i = ms.hero_pos() + var flags_before: int = ms.flag_count() + # ---- 相 0:夹具全集合法九型 ⇒ 作用集 == 独立重算期望集(V1)---- + var snap: Dictionary = view.type_glow_probe() + check(bool(snap.get("enabled", false)), "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)") + check((snap.get("errors", []) as Array).is_empty(), "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)") + var expect: Dictionary = _glow_expected_set(b, ms) + check(_glow_key_set_equal(snap.get("cells", {}), expect), + "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" % [expect.size(), (snap.get("cells", {}) as Dictionary).size()]) + check(expect.size() >= 5, "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" % [expect.size(), steps]) + # 已开格/r0 出生展开区零微光(S1:已开格不显示) + var opened_leak := "" + for c in ms.revealed_cells(): + if (snap.get("cells", {}) as Dictionary).has(c): + opened_leak += str(c) + " " + for c in b.compute_r0(): + if (snap.get("cells", {}) as Dictionary).has(c): + opened_leak += "r0:" + str(c) + " " + check(opened_leak == "", "V1 已开格/r0 出生展开区零微光(S1)%s" % ("" if opened_leak == "" else "违例:" + opened_leak)) + # ---- 相 1:八邻派定八型 ⇒ 五组逐格映射(V2 活板面)---- + var nbs: Array = b.neighbors8(hero) + var assign := ["attack", "heal", "pierce", "blast", "magic", "defense", "action", "gp"] + var expect_groups := {"attack": UiKit.GLOW_BUFF, "defense": UiKit.GLOW_BUFF, "weaken": UiKit.GLOW_BUFF, + "action": UiKit.GLOW_BUFF, "gp": UiKit.GLOW_BUFF, "heal": UiKit.GLOW_HEAL, + "pierce": UiKit.GLOW_PIERCE, "blast": UiKit.GLOW_AOE, "magic": UiKit.GLOW_MAGIC} + var mapped := 0 + for i in mini(assign.size(), nbs.size()): + var nb: Vector2i = nbs[i] + if ms.is_revealed(nb) or ms.is_removed(nb): + continue # 已开邻格不派定——只测未开格 + _glow_type_overrides[nb] = assign[i] + mapped += 1 + view.refresh() + snap = view.type_glow_probe() + check(mapped >= 5, "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" % mapped) + var map_bad := "" + for i in mini(assign.size(), nbs.size()): + var nb2: Vector2i = nbs[i] + if not _glow_type_overrides.has(nb2): + continue + var got: int = int((snap.get("cells", {}) as Dictionary).get(nb2, 0)) + if got != int(expect_groups[assign[i]]): + map_bad += "%s:%s→组%d(期望组%d) " % [str(nb2), assign[i], got, int(expect_groups[assign[i]])] + check(map_bad == "", "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" + % ("" if map_bad == "" else "违例:" + map_bad)) + # ---- 相 2:清单外 cell_type ⇒ 显式登记 + 该格无微光(V2 禁静默空光)---- + var bogus_cell: Vector2i = hero + for i in mini(assign.size(), nbs.size()): + var nb3: Vector2i = nbs[i] + if _glow_type_overrides.has(nb3): + bogus_cell = nb3 + break + _glow_type_overrides[bogus_cell] = "keystone" # 大宝箱未来第十型之外的清单外值 + view.refresh() + snap = view.type_glow_probe() + var errs: Array = snap.get("errors", []) + check(errs.has("keystone"), "V2 清单外 cell_type 显式登记(errors=%s)" % str(errs)) + check(not (snap.get("cells", {}) as Dictionary).has(bogus_cell), + "V2 清单外格零微光(无静默空光——%s 不在作用集)" % str(bogus_cell)) + check((snap.get("cells", {}) as Dictionary).size() == mapped - 1, + "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" % [mapped, (snap.get("cells", {}) as Dictionary).size()]) + # ---- 相 3:封锁格(query 返回 null)⇒ 无微光且零登记 ---- + _glow_type_overrides.erase(bogus_cell) # 复位清单外(errors 保留=板级登记语义) + var blocked_cell: Vector2i = hero + for i in mini(assign.size(), nbs.size()): + var nb4: Vector2i = nbs[i] + if _glow_type_overrides.has(nb4): + blocked_cell = nb4 + break + _glow_blocked[blocked_cell] = true + view.refresh() + snap = view.type_glow_probe() + check(not (snap.get("cells", {}) as Dictionary).has(blocked_cell), + "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" % str(blocked_cell)) + check((snap.get("errors", []) as Array).size() == 1, + "V1 封锁格零登记(errors 仍仅清单外一条——%s)" % str(snap.get("errors", []))) + check((snap.get("cells", {}) as Dictionary).size() == mapped - 1, + "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" % (snap.get("cells", {}) as Dictionary).size()) + # ---- 相 4:封锁解除 ⇒ 作用集复位(进出圈复亮走回)---- + _glow_blocked.erase(blocked_cell) + view.refresh() + snap = view.type_glow_probe() + check(_glow_key_set_equal(snap.get("cells", {}), expect), + "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" % [expect.size(), (snap.get("cells", {}) as Dictionary).size()]) + # ---- 相 5:开关 off/on(V7——off 态空+on 态逐位复现+棋盘态零扰动)---- + var snap_before_off: Dictionary = view.type_glow_probe() + view.set_type_glow_enabled(false) + snap = view.type_glow_probe() + check(not bool(snap.get("enabled", true)), "V7 off 态开关读数=false") + check((snap.get("cells", {}) as Dictionary).is_empty(), "V7 off 态作用集为空(基线保护面)") + view.set_type_glow_enabled(true) + var snap_on: Dictionary = view.type_glow_probe() + check(bool(snap_on.get("enabled", false)), "V7 on 态开关读数=true(复现)") + check(_glow_map_equal(snap_on.get("cells", {}), snap_before_off.get("cells", {})), + "V7 off→on 作用集逐位复现(关前 %d 格/开后 %d 格,组号逐位一致)" % [ + (snap_before_off.get("cells", {}) as Dictionary).size(), (snap_on.get("cells", {}) as Dictionary).size()]) + check(_glow_key_set_equal(snap_on.get("cells", {}), expect), + "V7 off→on 作用集与独立重算期望集恒等(%d 格)" % (snap_on.get("cells", {}) as Dictionary).size()) + var revealed_after: Array = ms.revealed_cells() + revealed_after.sort() + check(revealed_before == revealed_after and hero_before == ms.hero_pos() and flags_before == ms.flag_count(), + "V7 微光开关零玩法写入(已开集/英雄位/旗数三面对照逐位一致)") + # ---- 相 6:换图随 rebuild 重算(V3——旧板坐标零残留)---- + _glow_type_overrides.clear() + _glow_blocked.clear() + var b2 = board_generator.generate(1, 0, _mk_stream("board_gen", 909), _db) + var ae2 = load("res://core/action_economy.gd").new(_db) + var ms2 = load("res://core/minesweeper_rules.gd").new(_db, ae2) + ms2.install_board(b2) + ae2.notify_round_start() + view.rebuild(b2, ms2) + var snap2: Dictionary = view.type_glow_probe() + var expect2: Dictionary = _glow_expected_set(b2, ms2) + check(_glow_key_set_equal(snap2.get("cells", {}), expect2), + "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()]) + check((snap2.get("errors", []) as Array).is_empty(), "V3 换图清清单外登记(rebuild 清零)") + view.free() + # ---- 相 7:同板同序列 on/off 双实例结算逐位一致(契约 1 端到端面——不新起跑批)---- + # 两个平行 core(同 db/同一 BoardData 产物/同初始展开),一侧挂生产默认开态 view、一侧挂 + # 显式 off 态 view(差异唯一定位在类型微光开关——与渲染取证双帧同款隔离思想);同一动作 + # 序列逐步对照摘要,on 侧若在微光派生中意外写板态,两侧即分叉。 + var aeA = load("res://core/action_economy.gd").new(_db) + var msA = load("res://core/minesweeper_rules.gd").new(_db, aeA) + var aeB = load("res://core/action_economy.gd").new(_db) + var msB = load("res://core/minesweeper_rules.gd").new(_db, aeB) + msA.install_board(b) + msB.install_board(b) + aeA.notify_round_start() + aeB.notify_round_start() + var viewA = load("res://presentation/views/board_view.gd").new() + viewA.set_identity_resolver(_glow_fixture_identity) + viewA.rebuild(b, msA) + viewA.set_dig_glow_enabled(true) + var viewB = load("res://presentation/views/board_view.gd").new() + viewB.set_identity_resolver(_glow_fixture_identity) + viewB.rebuild(b, msB) + viewB.set_dig_glow_enabled(true) + viewB.set_type_glow_enabled(false) # off 态 = 基线保护面 + var diverge := "" + var seq_steps := 0 + while seq_steps < 12: + var act7: Dictionary = AutoPlayer.novice_action(msA) + if String(act7.get("kind", "stop")) == "stop": + break + var op7 := String(act7.get("kind", "")) + if op7 == "flag": + op7 = "flag_place" + var batch7 := [{"action": op7, "cell": act7.get("cell")}] + msA.settle_batch(batch7) + msB.settle_batch(batch7) + viewA.refresh() + viewB.refresh() + if _ms_digest(msA, aeA) != _ms_digest(msB, aeB): + diverge = "第 %d 步(%s)后摘要分叉" % [seq_steps, op7] + break + seq_steps += 1 + check(diverge == "", "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" + % [seq_steps, diverge]) + check(seq_steps >= 5, "契约 1 对照前提:同序列 ≥5 步(实测 %d)" % seq_steps) + viewA.free() + viewB.free() + + +# 排雷 core 态摘要(同板同序列对照面——已开记录含 kind/digit、旗集、英雄位、AP、相位) +func _ms_digest(ms, ae) -> Array: + var b: Variant = ms.current_board() + var rev: Array = [] + for c in ms.revealed_cells(): + var rec: Dictionary = ms.reveal_record(c) + rev.append([int(c.x), int(c.y), String(rec.get("kind", "")), int(rec.get("digit", -1))]) + rev.sort() + var flags: Array = [] + for y in b.height: + for x in b.width: + var c := Vector2i(x, y) + if ms.has_flag(c): + flags.append([int(c.x), int(c.y)]) + return [[int(ms.hero_pos().x), int(ms.hero_pos().y)], rev, flags, ae.ap, ae.phase, ms.flag_count()] + + # ---- 地图战斗资源·身份面单元冒烟(格面资源批:roll 恰一次 / 雷安同池 / 幂等 / 清图批尾)---- func _smoke_reward_identity_unit() -> void: diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index 4a9b2fbf..059b1a55 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -492,8 +492,108 @@ func _ready() -> void: await get_tree().process_frame if not _has_action(status_dig, "dig") or not ms.is_revealed(status_dig): _problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)") + # ---- case⑰ 未开格类型微光(迭代 30 卡① T2-③——活板面走查:作用集恒等/开格灭/移动复亮 + # 与出圈灭/相位门控/开关两态零扰动。与冒烟段数据面断言分工:此处走真实点击路径) ---- + if not bool(bv.type_glow_probe().get("enabled", false)): + _problems.append("case⑰ 微光默认态非开(生产默认开——Q4 丙案)") + if not (bv.type_glow_probe().get("errors", []) as Array).is_empty(): + _problems.append("case⑰ 活板清单外登记非空(errors=%s——生产数据应全集合法)" % str(bv.type_glow_probe().get("errors", []))) + if bv.diggable_probe().is_empty(): + var mv17a := _pick_frontier(bv, ms) + if mv17a.x == -9: + _problems.append("case⑰ 板面无 diggable 且无前沿可迫近(活板面前提异常)") + else: + _push_click(_pos(bv, mv17a), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + var digp17: Dictionary = bv.diggable_probe() + if digp17.is_empty(): + _problems.append("case⑰ 板面仍无 diggable(活板面前提异常)") + else: + # ① 作用集恒等:cells 键集 == diggable 键集(生产身份回调面——未开格恒可查,F4) + var keys17: Array = (bv.type_glow_probe().get("cells", {}) as Dictionary).keys() + var eq17 := keys17.size() == digp17.size() + if eq17: + for k in keys17: + if not digp17.has(k): + eq17 = false + break + if not eq17: + _problems.append("case⑰ 作用集与可挖域不等(cells=%d diggable=%d)" % [keys17.size(), digp17.size()]) + # ② 开格灭:真实点击挖开一格 → 该格退出作用集(S3 生命周期) + var dig17 := Vector2i(-9, -9) + for c in digp17: + if not ms.has_flag(c): + dig17 = c + break + if dig17.x == -9: + _problems.append("case⑰ 无无旗可挖格(板面异常)") + else: + if not (bv.type_glow_probe().get("cells", {}) as Dictionary).has(dig17): + _problems.append("case⑰ 待挖格不在作用集(%s)" % str(dig17)) + _push_click(_pos(bv, dig17), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + if not ms.is_revealed(dig17): + _problems.append("case⑰ 真实点击未挖开目标格(%s)" % str(dig17)) + if (bv.type_glow_probe().get("cells", {}) as Dictionary).has(dig17): + _problems.append("case⑰ 开格未灭(%s 仍在作用集——S3 生命周期破)" % str(dig17)) + # ③ 移动复亮/出圈灭:点击前沿已开格移英雄 → 作用集随新 diggable 重算(恒等面承载两者) + var mv17 := _pick_frontier(bv, ms) + if mv17.x == -9: + _problems.append("case⑰ 无前沿可迫近(移动复亮断言取格中断)") + else: + _push_click(_pos(bv, mv17), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + var glowp2: Dictionary = bv.type_glow_probe() + var digp18: Dictionary = bv.diggable_probe() + var keys18: Array = (glowp2.get("cells", {}) as Dictionary).keys() + var eq18 := keys18.size() == digp18.size() + if eq18: + for k in keys18: + if not digp18.has(k): + eq18 = false + break + if not eq18: + _problems.append("case⑰ 移动后作用集与新可挖域不等(cells=%d diggable=%d——复亮/出圈灭破)" % [keys18.size(), digp18.size()]) + # ④ 相位门控:结算相位(_dig_glow off)作用集空、微光开关读数不变;回挖掘相位逐位复位 + var before_phase: Dictionary = (bv.type_glow_probe().get("cells", {}) as Dictionary).duplicate() + bv.set_dig_glow_enabled(false) + var glowp4: Dictionary = bv.type_glow_probe() + if not (glowp4.get("cells", {}) as Dictionary).is_empty(): + _problems.append("case⑰ 结算相位未灭(作用集 %d 格——相位门控破)" % (glowp4.get("cells", {}) as Dictionary).size()) + if not bool(glowp4.get("enabled", false)): + _problems.append("case⑰ 相位门控误改微光开关态(enabled 应保持 true)") + bv.set_dig_glow_enabled(true) + var glowp5: Dictionary = bv.type_glow_probe() + var after_phase: Dictionary = glowp5.get("cells", {}) + var eq_phase := after_phase.size() == before_phase.size() + if eq_phase: + for k in before_phase: + if int(after_phase.get(k, -1)) != int(before_phase[k]): + eq_phase = false + break + if not eq_phase: + _problems.append("case⑰ 回挖掘相位作用集未逐位复位(%d→%d)" % [before_phase.size(), after_phase.size()]) + # ⑤ 开关两态零扰动(case⑮ 同款升级到全板 revealed 摘要):off 前后 ms 逻辑态逐位一致 + var b17 = ms.current_board() + var digest_before17: Array = [] + for y in b17.height: + for x in b17.width: + digest_before17.append(ms.is_revealed(Vector2i(x, y))) + bv.set_type_glow_enabled(false) + var digest_off17: Array = [] + for y in b17.height: + for x in b17.width: + digest_off17.append(ms.is_revealed(Vector2i(x, y))) + if digest_off17 != digest_before17: + _problems.append("case⑰ 微光开关改写逻辑态(两态结算逐位一致 🔴 契约破)") + bv.set_type_glow_enabled(true) + if not bool(bv.type_glow_probe().get("enabled", false)): + _problems.append("case⑰ 微光开关未恢复开态") if _problems.is_empty(): - print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾/状态面板输入锁 十七断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕,⑯状态面板输入锁 2026-10-06 迭代17卡③增)") + print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾/状态面板输入锁/未开格类型微光 十八断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕,⑯状态面板输入锁 2026-10-06 迭代17卡③增,⑰未开格类型微光 2026-10-17 迭代30卡①增)") get_tree().quit(0) else: for p in _problems: diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 48f95512..aee451bb 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -77,6 +77,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/config_db_cross_validator_test.gd", "expect": 0}, # 跨表校验(正向零错/悬空 skill_id/side 反向) {"path": "res://tests/unit/presentation/hud_presenter_test.gd", "expect": 0}, # HUD 投影文案(逐拒绝原因/兜底不空) {"path": "res://tests/unit/presentation/battle_narrator_test.gd", "expect": 0}, # 战斗叙述(数值带入/胜负互异/收束态恒定) + {"path": "res://tests/unit/presentation/type_glow_test.gd", "expect": 0}, # 迭代 30 卡① T2-②——未开格类型微光数据面(九型归组封闭/清单外显式失败/冻结色相只准动明度/α170·带宽cell÷8锚定/合成空间阶梯单调+模型CR≥1.20/probe API 形状) ] diff --git a/game/tests/unit/presentation/type_glow_test.gd b/game/tests/unit/presentation/type_glow_test.gd new file mode 100644 index 00000000..8fa16e9c --- /dev/null +++ b/game/tests/unit/presentation/type_glow_test.gd @@ -0,0 +1,173 @@ +# tests/unit/presentation/type_glow_test.gd — 未开格类型微光数据面直验(迭代 30 卡① T2-②) +# 断言面 = 纯函数/常量域(无渲染):① 九 cell_type → 五组封闭判定(含清单外显式失败、 +# 禁子串匹配、与卡顶带 tg_band 的归组差异锚定——Q3 甲案两带并存);② 五组色值冻结面 +# (HSV 对照 art-bible §4.2 冻结值只准动明度档——2026-10-17 群伤组一案停机呈报后用户裁 +# #A61D37,本测试即该裁定的守门件);③ 呈现参数锚定(α170/带宽 cell÷8);④ 合成空间 +# 阶梯单调 + 相邻组模型 CR ≥1.20(色弱灰度互辨的数据面预判,终审在渲染取证探针实测); +# ⑤ probe API 形状(默认开/空态键集)。 +# 运行:godot --headless --path game --script res://tests/unit/presentation/type_glow_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +# --script 模式无全局标识符(class_name 不可用)——使用方 preload +const UiKitScript := preload("res://presentation/views/ui_kit.gd") +const BoardViewScript := preload("res://presentation/views/board_view.gd") + +# art-bible §4.2 样张 v2 冻结五组(F3 抄录表——本测试的冻结面基准) +const FROZEN := { + "BUFF": Color8(0xD6, 0xC1, 0x3A), "HEAL": Color8(0x5C, 0xB8, 0x3A), + "PIERCE": Color8(0x2E, 0x9E, 0x9E), "MAGIC": Color8(0x7B, 0x5B, 0xD6), + "AOE": Color8(0xFF, 0x2D, 0x55), +} +# 九 cell_type 全值域 → 期望组号(art-bible §4.2 成员集合逐字照抄) +const TYPE_EXPECT := { + "attack": UiKitScript.GLOW_BUFF, "defense": UiKitScript.GLOW_BUFF, + "weaken": UiKitScript.GLOW_BUFF, "action": UiKitScript.GLOW_BUFF, + "gp": UiKitScript.GLOW_BUFF, "heal": UiKitScript.GLOW_HEAL, + "pierce": UiKitScript.GLOW_PIERCE, "blast": UiKitScript.GLOW_AOE, + "magic": UiKitScript.GLOW_MAGIC, +} +# provisional 明度档终值(2026-10-17 用户裁后)——逐位锚定,谁改色值这里红 +const IMPL := { + "BUFF": Color8(247, 223, 67), "HEAL": Color8(104, 208, 66), + "PIERCE": Color8(48, 166, 166), "MAGIC": Color8(123, 91, 214), + "AOE": Color8(166, 29, 55), +} +# 实现常量直引(禁 class get() 动态取——parse 层就不许) +const IMPL_CONSTS := { + "BUFF": UiKitScript.TG_GLOW_BUFF, "HEAL": UiKitScript.TG_GLOW_HEAL, + "PIERCE": UiKitScript.TG_GLOW_PIERCE, "MAGIC": UiKitScript.TG_GLOW_MAGIC, + "AOE": UiKitScript.TG_GLOW_AOE, +} +const CONST_NAME := { + "BUFF": "TG_GLOW_BUFF", "HEAL": "TG_GLOW_HEAL", "PIERCE": "TG_GLOW_PIERCE", + "MAGIC": "TG_GLOW_MAGIC", "AOE": "TG_GLOW_AOE", +} + + +func _init() -> void: + _test_grouping_closed() + _test_frozen_hue_face() + _test_render_params() + _test_gamma_ladder() + _test_probe_api_shape() + quit(Kit.finish("type_glow_test")) + + +# ① 归组封闭:九值全域映射+清单外显式失败+禁子串+两带差异锚定 +func _test_grouping_closed() -> void: + for t in TYPE_EXPECT: + var got: int = UiKitScript.glow_group_for_cell_type(t) + Kit.check("九型封闭:%s → 组%d(期望组%d)" % [t, got, int(TYPE_EXPECT[t])], got == int(TYPE_EXPECT[t])) + # 清单外值 = GLOW_NONE(调用方显式登记,禁静默空光;大宝箱未来第十型落数据时必须同步加归组, + # 否则本通道报红——这是该纪律的机检防线) + for bad in ["keystone", "", "ATTACK", "aoe", "blast_mine", " heal"]: + Kit.check("清单外值「%s」→ GLOW_NONE(禁静默空光)" % bad, + UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE) + # 与卡顶带 tg_band 的归组差异锚定(Q3 甲案两带并存、禁互引):blast 在格面带归群伤、 + # 在卡顶带归魔法(E5 回滚既存行为)——若将来误把两带统一,本断言红 + var band_blast: Color = UiKitScript.tg_band("blast_charge") + Kit.check("两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)", band_blast == UiKitScript.TG_MAGIC) + Kit.check("两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)", + UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE) + # 色值出口:五组各取本组常量;无组/越界组号 → 全透明(不画) + Kit.check("glow_color_for_group 群伤组 = TG_GLOW_AOE", + UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE) + Kit.check("glow_color_for_group 无组(GLOW_NONE)→ 全透明", + UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0)) + Kit.check("glow_color_for_group 越界组号 → 全透明", + UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0)) + + +# ② 冻结色相面:逐组 HSV 对照 art-bible §4.2 冻结值——只准动明度(ΔH≤0.2°/ΔS≤0.01) +func _test_frozen_hue_face() -> void: + for g in FROZEN: + var f: Color = FROZEN[g] + var m: Color = IMPL[g] + var impl_const: Color = IMPL_CONSTS[g] + Kit.check("实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" % [CONST_NAME[g], + int(m.r8), int(m.g8), int(m.b8)], impl_const == m) + var dh: float = absf(m.h - f.h) + dh = minf(dh, 1.0 - dh) # 色相环回绕 + var ds: float = absf(m.s - f.s) + Kit.check("冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" % [g, dh * 360.0, ds], + dh <= 0.2 / 360.0 and ds <= 0.01) + # 群伤组专锚(2026-10-17 停机呈报→用户裁 #A61D37):H/S 与冻结逐位一致、V=0.65 档 + var aoe_f: Color = FROZEN["AOE"] + var aoe_m: Color = UiKitScript.TG_GLOW_AOE + Kit.check("群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" % (absf(aoe_m.h - aoe_f.h) * 360.0), + absf(aoe_m.h - aoe_f.h) < 0.001) + Kit.check("群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" % absf(aoe_m.s - aoe_f.s), + absf(aoe_m.s - aoe_f.s) <= 0.01) + Kit.check("群伤组专项:V=0.65 明度档( provisional 域内)", absf(aoe_m.v - 166.0 / 255.0) < 0.002) + + +# ③ 呈现参数锚定(art-bible §4.2 v2 冻结参数) +func _test_render_params() -> void: + Kit.check("泛光强度 α = 170/255(v2 呈现参数冻结)", + is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0)) + Kit.check("带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)", + is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125)) + Kit.check("组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)", + UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 + and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5) + + +# ④ 合成空间阶梯:模型域单调 + 相邻组 CR ≥1.20(色弱互辨数据面预判;终审=探针实测) +func _test_gamma_ladder() -> void: + # 合成模型(ui_kit 头注登记口径):带内均值 ≈ 0.49×色值 + 0.51×瓦片(BRICK 未开掩尘地砖) + var k := 0.49 + var tile: Color = UiKitScript.BRICK + var comp := func(c: Color) -> Color: + return Color(k * c.r + (1.0 - k) * tile.r, k * c.g + (1.0 - k) * tile.g, k * c.b + (1.0 - k) * tile.b) + var order := ["BUFF", "HEAL", "PIERCE", "MAGIC", "AOE"] + var prev_lum := 2.0 + for g in order: + var cc: Color = comp.call(IMPL[g]) + var lum: float = _rel_lum709(cc) + Kit.check("阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" % [g, lum, prev_lum], lum < prev_lum) + prev_lum = lum + for i in range(order.size() - 1): + var a: Color = comp.call(IMPL[order[i]]) + var b: Color = comp.call(IMPL[order[i + 1]]) + var cr: float = _wcag_cr(a, b) + Kit.check("相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" % [order[i], order[i + 1], cr], cr >= 1.20) + # 首版排反的那一对专项:群伤线性亮度必须低于魔法(线性域序 = WCAG 判据域) + var aoe_lin: float = _rel_lum_lin(comp.call(IMPL["AOE"])) + var magic_lin: float = _rel_lum_lin(comp.call(IMPL["MAGIC"])) + Kit.check("群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" % [aoe_lin, magic_lin], + aoe_lin < magic_lin) + + +# ⑤ probe API 形状:默认开+空态键集(无板直取——不依赖棋盘装配) +func _test_probe_api_shape() -> void: + var view = BoardViewScript.new() + var snap: Dictionary = view.type_glow_probe() + Kit.check("probe 键集 = {enabled, cells, errors}", + snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3) + Kit.check("probe 默认态:enabled=true(Q4 丙案生产默认开)", bool(snap.get("enabled", false))) + Kit.check("probe 默认态:作用集空(无板未派生)", (snap.get("cells", {}) as Dictionary).is_empty()) + Kit.check("probe 默认态:清单外登记空", (snap.get("errors", []) as Array).is_empty()) + view.set_type_glow_enabled(false) + Kit.check("set_type_glow_enabled(false) → probe enabled=false(无板不崩)", + not bool(view.type_glow_probe().get("enabled", true))) + view.free() + + +# Rec.709 相对亮度(sRGB 值域——灰度互辨通道读数,与 shot_checker 同口径) +static func _rel_lum709(c: Color) -> float: + return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b + + +# WCAG 线性域相对亮度(反伽马) +static func _rel_lum_lin(c: Color) -> float: + var lin := func(ch: float) -> float: + return ch / 12.92 if ch <= 0.04045 else pow((ch + 0.055) / 1.055, 2.4) + return 0.2126 * lin.call(c.r) + 0.7152 * lin.call(c.g) + 0.0722 * lin.call(c.b) + + +# WCAG 对比对(线性域 +0.05 偏移) +static func _wcag_cr(a: Color, b: Color) -> float: + var la: float = _rel_lum_lin(a) + var lb: float = _rel_lum_lin(b) + return (maxf(la, lb) + 0.05) / (minf(la, lb) + 0.05) diff --git a/game/tests/unit/presentation/type_glow_test.gd.uid b/game/tests/unit/presentation/type_glow_test.gd.uid new file mode 100644 index 00000000..6564ad93 --- /dev/null +++ b/game/tests/unit/presentation/type_glow_test.gd.uid @@ -0,0 +1 @@ +uid://bykt02pgt6mbf