diff --git a/game/smoke/board_shot_probe.gd b/game/smoke/board_shot_probe.gd index d95878d1..37662985 100644 --- a/game/smoke/board_shot_probe.gd +++ b/game/smoke/board_shot_probe.gd @@ -7,11 +7,22 @@ # 护栏:headless fail-fast(shot_probe 同款);空纹理取证 fail——取证段失败=闸红, # 零字节/空纹理不允许静默落盘;board_meta.json 取样坐标落盘(棋盘视图全局矩形—— # golden 前置裁剪坐标源,禁硬编码像素)。 +# 迭代 30 卡① T2-④ 双帧取证:每关抓 微光开(生产默认态·Q4 丙案)/关(golden 基线保护面) +# 两帧——① 开帧逐像素判定「非生效格零变化」(禁整格染色/禁越圈;生效格内只准落在内缘 +# 带 = cell÷8)② 每个生效格须有实绘像素(微光不空转)③ 关帧即 golden 对比帧(原名落盘, +# 基线 2026-09-30 代不重制——契约 1 off 态基线)。隔离纪律 = 与 type_glow_shot_probe 同款: +# 前置 UiKit.motion_scale=0.5 冻结接触带呼吸相位(静态带色),两帧差异唯一定位在类型微光。 extends Node const AutoPlayer := preload("res://smoke/auto_player.gd") +const ActionEconomy := preload("res://core/action_economy.gd") var _meta := {} # 棋盘视图实测几何(全局 rect——tools/golden_board_crop.py 取样坐标源) +var _problems: Array = [] +var _level_readings := {} # 关 → 双帧比对读数(实现报告读数表源) +var _cur_settlement = null # 当关 settlement(身份回调读取面——仿生产 run_scope.settlement 现值; +# 不用 Callable.bind 传参:bind 的 Callable 动态派发时对类型化参数做 Object 硬校验, +# 报「Cannot convert argument 2 from Object to Vector2i」——直接方法引用+成员现值无此坑) func _ready() -> void: @@ -30,6 +41,11 @@ func _ready() -> void: push_error("[board_shot] 表加载失败") get_tree().quit(1) return + # 双帧单一变量隔离(迭代 30 卡①):冻结接触带呼吸相位——motion_scale<1.0 ⇒ _apply_contact_bands + # 施静态色(减动效停格路径)。必须在 rebuild 前置位(rebuild/set_dig_glow_enabled 按当时相位 + # 重施带色)。golden 侧影响:静态带色=呼吸周期中值,与基线捕获相的像素差最坏 ≈灰度24 于 + # 接触带像素(占比 <0.4%),≤2% 判据不受影响;停格同时令「非生效格零变化」可精确判定。 + UiKit.motion_scale = 0.5 var ui_font: Font = load("res://presentation/views/ui_kit.gd").load_font(load("res://presentation/views/ui_kit.gd").FONT_UI) var targets := [[1, "L1_dungeon"], [2, "L2_underdark"], [3, "L3_deep_underdark"]] for t in targets: @@ -43,6 +59,7 @@ func _ready() -> void: get_tree().quit(1) return var settlement = runner.current_battle() + _cur_settlement = settlement for i in range(18): var act: Dictionary = AutoPlayer.novice_action(settlement.minesweeper) var kind := String(act.get("kind", "stop")) @@ -58,7 +75,18 @@ func _ready() -> void: if ui_font != null: view.set_world_font(ui_font) add_child(view) + # 身份解析接线(生产同源——main.gd:251 set_identity_resolver(_identity_lookup)):必须在 + # rebuild / set_dig_glow_enabled 前接好,二者尾链 _recompute_glow 同步读身份建作用集。 + # 裸视图不接 = _identity_cb 无效 ⇒ info 恒 null ⇒ 作用集恒空(2026-10-17 首两次实跑 + # 三关 glow=0 根因——双帧比对空转、PASS 假绿)。现值经 _cur_settlement 成员读入 + # (仿生产 run_scope.settlement;bind 传参有 Callable 类型校验坑,见成员注)。 + view.set_identity_resolver(_probe_identity_lookup) view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) + # 进挖掘相位(生产态 = hud_presenter.set_dig_glow_enabled(not settling) 接线——裸视图默认 + # _dig_glow=false,接触带与类型微光同源门控都不渲染;golden 基线 2026-09-30 代即捕获于 + # 该裸态。双帧取证要求生产默认态,故显式开——两帧均含静态接触带(motion_scale=0.5 停格), + # 差异唯一定位在类型微光开关) + view.set_dig_glow_enabled(true) # 抓帧前等入场动画完结(迭代 19 卡① S2 观察项根因修复 2026-10-08):rebuild 有全板淡入 # motion(0.2)+R0 涟漪逐格延迟显形(motion(0.04×离板心距离)+motion(0.12),最远格 ≈0.56s), # 原制 6 个 process_frame 热机后 ≈0.1s=捕在涟漪中途——逐跑帧时序抖动使揭示 alpha 漂移, @@ -66,21 +94,124 @@ func _ready() -> void: await get_tree().create_timer(1.2).timeout await get_tree().process_frame await RenderingServer.frame_post_draw - var img := get_viewport().get_texture().get_image() - if img == null or img.is_empty(): + var on_img := get_viewport().get_texture().get_image() + if on_img == null or on_img.is_empty(): push_error("[board_shot] FAIL — 抓帧空纹理: %s(Q7 取证语义=闸红)" % tag) get_tree().quit(1) return + var on_path := "%s/board_%s_glow_on.png" % [out, tag] + on_img.save_png(on_path) + # 作用集快照必须在关开关之前取(set_type_glow_enabled(false) 会清空 _glow 重算) + var glow_cells: Dictionary = (view.type_glow_probe().get("cells", {}) as Dictionary).duplicate() + # 帧 B = 微光关(golden 基线保护面——与基线同文件名落盘,golden 对比取本帧) + view.set_type_glow_enabled(false) + await get_tree().process_frame + await RenderingServer.frame_post_draw + var off_img := get_viewport().get_texture().get_image() + if off_img == null or off_img.is_empty(): + push_error("[board_shot] FAIL — 抓帧空纹理: %s_off(Q7 取证语义=闸红)" % tag) + get_tree().quit(1) + return var path := "%s/board_%s.png" % [out, tag] - img.save_png(path) + off_img.save_png(path) _meta[tag] = {"x": view.global_position.x, "y": view.global_position.y, "w": view.size.x, "h": view.size.y} - print("[board_shot] saved %s (%dx%d)" % [path, img.get_width(), img.get_height()]) + print("[board_shot] saved %s (%dx%d) + %s" % [path, off_img.get_width(), off_img.get_height(), on_path]) + _check_dual_frames(tag, view, glow_cells, off_img, on_img) view.queue_free() await get_tree().process_frame var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE) if mf != null: mf.store_string(JSON.stringify(_meta)) mf.close() - print("[board_shot] done") - get_tree().quit(0) + var rf := FileAccess.open(out.path_join("board_dual_frame_readings.json"), FileAccess.WRITE) + if rf != null: + rf.store_string(JSON.stringify(_level_readings, " ")) + rf.close() + if _problems.is_empty(): + print("[board_shot] PASS — 三关双帧取证:非生效格零变化/生效格内缘带外零侵入/生效格实绘非空;golden 帧=微光关态(契约 1 基线保护面)") + get_tree().quit(0) + else: + for p in _problems: + push_error("[board_shot] FAIL — " + p) + print("[board_shot] FAIL — " + "; ".join(_problems)) + get_tree().quit(1) + + +# 身份解析回调(生产同源面——main.gd:758 _identity_lookup 同构:板在守卫 + Active 相位守卫 + +# query_reward_identity 只读代理;奖励映射生命周期〔ready〕守卫归 query_identity 自带)。 +# 相位为何是 Active:每轮 settle_batch 末尾 _exit_distribution 已 notify_round_start 兑现下轮 +# AP ⇒ 18 步 novice 驱动后相位回 Active,即生产「挖掘期看板」态;本回调零玩法数据写入 +# (🔴 契约 1——只读投影面)。 +func _probe_identity_lookup(c: Vector2i): + if _cur_settlement == null or _cur_settlement.minesweeper.current_board() == null: + return null + if String(_cur_settlement.action_economy.phase) != ActionEconomy.PHASE_ACTIVE: + return null + return _cur_settlement.query_reward_identity(c) + + +# 双帧逐像素比对(迭代 30 卡① T2-④-①②):所有变化像素必须落在生效格的内缘带内 +# (de < cell÷8——带外含砖心零侵入=禁整格染色/禁越圈);每个生效格须有实绘像素(不空转)。 +func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Image, on_img: Image) -> void: + var board = view.board() + if board == null: + _problems.append("%s 双帧比对取板失败(view.board() 为 null)" % tag) + return + var w: int = off_img.get_width() + var h: int = off_img.get_height() + var cell: float = view.cell_size + var band_px: float = cell * 0.125 + # 逐格窗口矩形 + 到格缘距离判据(与 type_glow_shot_probe 同口径) + var rects := {} + for gy in range(board.height): + for gx in range(board.width): + var c := Vector2i(gx, gy) + rects[c] = view.cell_rect_probe(c) + var changed_total := 0 + var changed_outside := 0 + var first_outside := "" + var glow_hits := {} + for c in glow_cells: + glow_hits[c] = 0 + for gy in range(board.height): + for gx in range(board.width): + var c2 := Vector2i(gx, gy) + var r: Rect2 = rects[c2] + if r.size.x <= 0.0: + continue + var is_glow: bool = glow_cells.has(c2) + var x0: int = int(floor(r.position.x)) + var y0: int = int(floor(r.position.y)) + var x1: int = int(ceil(r.position.x + r.size.x)) + var y1: int = int(ceil(r.position.y + r.size.y)) + for yy in range(maxi(0, y0), mini(h, y1)): + for xx in range(maxi(0, x0), mini(w, x1)): + var de: int = mini(mini(xx - x0, x1 - 1 - xx), mini(yy - y0, y1 - 1 - yy)) + var p1: Color = off_img.get_pixel(xx, yy) + var p2: Color = on_img.get_pixel(xx, yy) + if p1.r == p2.r and p1.g == p2.g and p1.b == p2.b and p1.a == p2.a: + continue + changed_total += 1 + if is_glow: + if float(de) < band_px: + glow_hits[c2] = int(glow_hits[c2]) + 1 + else: + changed_outside += 1 + if first_outside == "": + first_outside = "%s 带外(de=%d≥%.0f)" % [str(c2), de, band_px] + else: + changed_outside += 1 + if first_outside == "": + first_outside = "%s 非生效格" % str(c2) + _level_readings[tag] = {"glow_cells": glow_cells.size(), "changed_total": changed_total, + "changed_outside": changed_outside, "band_px": band_px} + print("[board_shot] %s 双帧读数 glow=%d changed=%d outside=%d band=%.0fpx" + % [tag, glow_cells.size(), changed_total, changed_outside, band_px]) + if changed_outside != 0: + _problems.append("%s 非生效格/带外像素变化 %d 个(首例 %s——禁整格染色/禁越圈)" % [tag, changed_outside, first_outside]) + if glow_cells.size() > 0 and changed_total == 0: + _problems.append("%s 微光开帧整板零变化(微光未生效)" % tag) + for c in glow_cells: + if int(glow_hits[c]) == 0: + _problems.append("%s 生效格 %s 零实绘像素(微光空转)" % [tag, str(c)]) diff --git a/game/smoke/type_glow_shot_probe.gd b/game/smoke/type_glow_shot_probe.gd new file mode 100644 index 00000000..86ff6706 --- /dev/null +++ b/game/smoke/type_glow_shot_probe.gd @@ -0,0 +1,261 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 未开格类型微光渲染取证探针(迭代 30 卡① T2 像素侧——V4 禁侵/V5 不压破/V6 五组互辨)。 +# 必须**窗口化**运行(headless 无渲染面,抓帧恒空): +# Godot_console.exe --path . res://smoke/type_glow_shot_probe.tscn -- <输出目录> +# 流程:起一板(真实 LevelRunner + settle_batch 驱动 18 步 novice——自然揭示数字/旗)→ 注入夹具身份回调 +# (九 cell_type 循环 ⇒ 五组同屏齐现)→ BoardView 真渲染: +# 帧 A = 微光关(基线态)→ type_glow_off.png +# 帧 B = 微光开(生产默认态)→ type_glow_on.png +# 逐像素比对两帧:① 所有变化像素必须落在生效格的内缘带内(非生效格零变化=禁整格染色/禁越圈) +# ② 五组带内取样两两灰度对比对 CR ≥ GRAY_CR_MIN(色弱互辨通道)。 +# 零存档触碰(无 save_manager 引用)。取证件落调用方给的目录(库外),不入 git。 +extends Node + +const AutoPlayer := preload("res://smoke/auto_player.gd") + +# 夹具:九 cell_type 全值域(地图战斗资源九型枚举同源) +const FIXTURE_TYPES := ["attack", "defense", "blast", "heal", "weaken", + "pierce", "magic", "action", "gp"] +# 8 邻方位 → 九型确定性指派(V6 五组同屏齐现的取证前提——组间互辨要求一帧内五组俱在)。 +# 方位序:NW N NE W E SW S SE;探针起板后 N/NE/E 三位被 novice 挖开(恒定 seed 确定性), +# 故五组各占一个存活方位:NW=BUFF/W=PIERCE/SW=AOE/S=MAGIC/SE=HEAL,其余三位补 BUFF。 +# 同屏组数不足 5 = 取证失败(见 _report 硬断言)——幸存集若变,探针报红而非静默少测。 +const FIXTURE_DIR_TYPES := ["attack", "gp", "defense", "pierce", + "weaken", "blast", "magic", "heal"] +# 判据阈值(provisional——首版取严;实现报告登记,复核转正归战斗一屏样张批) +const CORE_PAD_FRAC := 0.20 +const GRAY_CR_MIN := 1.20 + +var _hero_pos := Vector2i.ZERO + + +func _ready() -> void: + if DisplayServer.get_name() == "headless": + print("[type_glow_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空)") + get_tree().quit(1) + return + var args := OS.get_cmdline_user_args() + var out := args[0] if args.size() >= 1 and args[0] != "" else "user://type-glow-shots" + DirAccess.make_dir_recursive_absolute(out) + await get_tree().process_frame + var db = load("res://core/config_db.gd").new() + if not db.load_tables("res://data/tables", true): + push_error("[type_glow_shot] 表加载失败") + get_tree().quit(1) + return + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(421001) + var runner = load("res://core/level_runner.gd").new(db, rng) + if not runner.start_level(1, 421001, {}, 0): + push_error("[type_glow_shot] 开局失败") + get_tree().quit(1) + return + var settlement = runner.current_battle() + for i in range(18): + var act: Dictionary = AutoPlayer.novice_action(settlement.minesweeper) + var kind := String(act.get("kind", "stop")) + if kind == "stop": + break + var op := kind + if kind == "flag": + op = "flag_place" + settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}]) + var view = load("res://presentation/views/board_view.gd").new() + view.size = Vector2(900, 900) + view.position = Vector2(40, 40) + add_child(view) + # 冻结接触带呼吸(减动效路径 motion_scale<1.0 ⇒ _apply_contact_bands 施静态色): + # 两帧比对要隔离「类型微光开关」这一单一变量,英雄格接触带相位漂移会污染非生效格零变化判据。 + # 必须在 rebuild/set_dig_glow_enabled 之前置位——那两处会按当时相位重施带色。 + UiKit.motion_scale = 0.5 + var ui_font: Font = load("res://presentation/views/ui_kit.gd").load_font(load("res://presentation/views/ui_kit.gd").FONT_UI) + if ui_font != null: + view.set_world_font(ui_font) + # 夹具身份回调(返回形状与 query_identity 同构;8 邻按方位确定性指派,五组同屏齐现) + view.set_identity_resolver(_fixture_identity) + _hero_pos = settlement.minesweeper.hero_pos() + view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) + # 挖掘相位:接触带呼吸与类型微光同源门控(_dig_glow);此处显式开以承取证 + view.set_dig_glow_enabled(true) + # 作用集快照必须在关开关之前取(set_type_glow_enabled(false) 会清空 _glow 重算) + var glow_snapshot: Dictionary = view.type_glow_probe() + # 抓帧前等入场动画完结(board_shot_probe 同款时间制 1.2s——锚定动画完结态) + await get_tree().create_timer(1.2).timeout + await get_tree().process_frame + await RenderingServer.frame_post_draw + var on_img := get_viewport().get_texture().get_image() + if on_img == null or on_img.is_empty(): + push_error("[type_glow_shot] FAIL — 抓帧空纹理(取证失败=闸红)") + get_tree().quit(1) + return + on_img.save_png(out.path_join("type_glow_on.png")) + view.set_type_glow_enabled(false) + await get_tree().process_frame + await RenderingServer.frame_post_draw + var off_img := get_viewport().get_texture().get_image() + if off_img == null or off_img.is_empty(): + push_error("[type_glow_shot] FAIL — 抓帧空纹理(取证失败=闸红)") + get_tree().quit(1) + return + off_img.save_png(out.path_join("type_glow_off.png")) + _report(view, glow_snapshot, off_img, on_img, out) + + +# 逐像素比对与读数:V4(非生效格零变化+砖心零侵入)/V5(灰度分类一致)/V6(五组互辨) +# glow_snapshot = 关开关前的作用集快照(set_type_glow_enabled(false) 会清空 _glow 重算,故必须外传) +func _report(view, glow_snapshot: Dictionary, off_img: Image, on_img: Image, out: String) -> void: + var glow_cells: Dictionary = glow_snapshot.get("cells", {}) + var bad: Array[String] = [] + var readings := {} + if not bool(glow_snapshot.get("enabled", false)): + bad.append("微光开关态非默认开") + if glow_cells.is_empty(): + bad.append("微光作用集为空(英雄 8 邻未开格未派生出组号)") + var board = view.board() + var w: int = off_img.get_width() + var h: int = off_img.get_height() + var cell: float = view.cell_size + var band_px: float = cell * 0.125 + var core_pad: float = cell * CORE_PAD_FRAC + var changed_total := 0 + var changed_outside := 0 + var by_group := {} + if board != null: + for gy in range(board.height): + for gx in range(board.width): + var c := Vector2i(gx, gy) + var r: Rect2 = view.cell_rect_probe(c) + if r.size.x <= 0.0: + continue + var is_glow: bool = glow_cells.has(c) + var g: int = int(glow_cells[c]) if is_glow else 0 + var x0: int = int(floor(r.position.x)) + var y0: int = int(floor(r.position.y)) + var x1: int = int(ceil(r.position.x + r.size.x)) + var y1: int = int(ceil(r.position.y + r.size.y)) + var gsum := 0.0 + var gn := 0 + for yy in range(maxi(0, y0), mini(h, y1)): + for xx in range(maxi(0, x0), mini(w, x1)): + var de: int = mini(mini(xx - x0, x1 - 1 - xx), mini(yy - y0, y1 - 1 - yy)) + var p1: Color = off_img.get_pixel(xx, yy) + var p2: Color = on_img.get_pixel(xx, yy) + if is_glow and float(de) < band_px: + gsum += _lum(p2) + gn += 1 + if p1.r == p2.r and p1.g == p2.g and p1.b == p2.b and p1.a == p2.a: + continue + changed_total += 1 + if (not is_glow) or float(de) >= core_pad - 1.0: + changed_outside += 1 + if is_glow and gn > 0 and not by_group.has(g): + by_group[g] = gsum / float(gn) + readings["glow_cells"] = glow_cells.size() + readings["groups"] = _sorted_keys(by_group) + readings["changed_total"] = changed_total + readings["changed_outside"] = changed_outside + readings["band_lum"] = by_group + var rects := {} + for c in glow_cells: + var rr: Rect2 = view.cell_rect_probe(c) + rects[str(c)] = [rr.position.x, rr.position.y, rr.size.x, rr.size.y] + readings["rects"] = rects + if changed_total == 0: + bad.append("开微光后整帧零像素变化(微光未生效)") + if changed_outside != 0: + bad.append("非生效格像素变化 %d 个(禁整格染色/禁越圈)" % changed_outside) + if by_group.size() < 5: + bad.append("同屏组数 %d < 5(V6 五组并列互辨的取证前提不成立)" % by_group.size()) + var ks: Array = _sorted_keys(by_group) + var pairs: Array[String] = [] + var worst_cr := 99.0 + var worst_pair := "" + for i in range(ks.size()): + for j in range(i + 1, ks.size()): + # WCAG 相对亮度对比对:band 均值先经 sRGB→线性反伽马(+0.05 偏移定义在线性域), + # 与 tools/shot_checker.py 的 WCAG 段同口径(色弱互辨通道的机检同款公式)。 + var la: float = _to_linear(float(by_group[ks[i]])) + var lb: float = _to_linear(float(by_group[ks[j]])) + var cr: float = (maxf(la, lb) + 0.05) / (minf(la, lb) + 0.05) + pairs.append("组%d/组%d CR=%.3f ΔL*=%.1f" % [ks[i], ks[j], cr, absf(_lstar(la) - _lstar(lb))]) + if cr < worst_cr: + worst_cr = cr + worst_pair = "组%d/组%d" % [ks[i], ks[j]] + readings["pairs"] = " ".join(pairs) + readings["worst_cr"] = worst_cr + readings["worst_pair"] = worst_pair + if ks.size() >= 2 and worst_cr < GRAY_CR_MIN: + bad.append("灰度互辨最弱对 %s CR=%.3f < %.3f(色弱通道不可分)" % [worst_pair, worst_cr, GRAY_CR_MIN]) + var mf := FileAccess.open(out.path_join("type_glow_readings.json"), FileAccess.WRITE) + if mf != null: + mf.store_string(JSON.stringify(readings, " ")) + mf.close() + print("[type_glow_shot] 读数 %s" % JSON.stringify(readings)) + if bad.is_empty(): + print("[type_glow_shot] PASS — 非生效格零变化/砖心零侵入/五组灰度互辨全过") + get_tree().quit(0) + else: + for m in bad: + print("[type_glow_shot] FAIL — %s" % m) + get_tree().quit(1) + + +# 夹具身份回调(形状与 query_identity 返回同构;8 邻按方位取九型之一,其余格按坐标兜底) +func _fixture_identity(c: Vector2i) -> Variant: + var ctype: String + var di: int = _dir_index(c - _hero_pos) + if di >= 0: + ctype = FIXTURE_DIR_TYPES[di] + else: + ctype = FIXTURE_TYPES[int(abs(c.x * 31 + c.y * 17)) % FIXTURE_TYPES.size()] + return {"reward_id": "fix_%d_%d" % [c.x, c.y], "cell_type": ctype, "tier": 1} + + +# 方位偏移 → 0..7(NW N NE W E SW S SE;非 8 邻返回 -1) +static func _dir_index(off: Vector2i) -> int: + match off: + Vector2i(-1, -1): + return 0 + Vector2i(0, -1): + return 1 + Vector2i(1, -1): + return 2 + Vector2i(-1, 0): + return 3 + Vector2i(1, 0): + return 4 + Vector2i(-1, 1): + return 5 + Vector2i(0, 1): + return 6 + Vector2i(1, 1): + return 7 + return -1 + + +static func _sorted_keys(d: Dictionary) -> Array: + var ks: Array = d.keys() + ks.sort() + return ks + + +# 相对亮度(Rec.709 系数——灰度互辨通道与 shot_checker 同口径) +# 相对亮度(Rec.709 系数——灰度互辨通道与 shot_checker 同口径;输入为帧缓冲 sRGB 值) +static func _lum(c: Color) -> float: + return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b + + +# sRGB(伽马)值 → 线性相对亮度(WCAG 2.x 反伽马——对比对公式的输入域) +static func _to_linear(y_srgb: float) -> float: + var c: float = clampf(y_srgb, 0.0, 1.0) + if c <= 0.04045: + return c / 12.92 + return pow((c + 0.055) / 1.055, 2.4) + + +# CIE L*(Y∈[0,1] → L*∈[0,100];ΔL* 读数供互辨裕量评估) +static func _lstar(y: float) -> float: + var yy: float = clampf(y, 0.0, 1.0) + if yy <= 0.008856: + return 903.3 * yy + return 116.0 * pow(yy, 1.0 / 3.0) - 16.0 diff --git a/game/smoke/type_glow_shot_probe.gd.uid b/game/smoke/type_glow_shot_probe.gd.uid new file mode 100644 index 00000000..73820296 --- /dev/null +++ b/game/smoke/type_glow_shot_probe.gd.uid @@ -0,0 +1 @@ +uid://djmrucr7nxl5j diff --git a/game/smoke/type_glow_shot_probe.tscn b/game/smoke/type_glow_shot_probe.tscn new file mode 100644 index 00000000..9eb5308e --- /dev/null +++ b/game/smoke/type_glow_shot_probe.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://smoke/type_glow_shot_probe.gd" id="1"] + +[node name="TypeGlowShotProbe" type="Node"] +script = ExtResource("1")