# eterngrn_polish_test —— EterGrnLib 对齐打磨(项 3/5/6)的 headless 自检。 # godot --headless --path project --script eterngrn_polish_test.gd # 项 1/2/4(材质 flag / LOD 淡入 / ease 曲线)是 C++ 内部 + 渲染,离线看不出,走目视。 extends SceneTree const CharShadow = preload("res://ui/char_shadow.gd") const PlayerCtl = preload("res://player_controller.gd") var _fail := 0 func _ck(c: bool, m: String) -> void: if not c: _fail += 1 printerr("FAIL: " + m) func _init() -> void: _run() if _fail == 0: print("PASS: eterngrn_polish_test") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: # ---- 5. CharShadow:Decal + 强制 cast_shadow ---- var root := Node3D.new() get_root().add_child(root) var mi := MeshInstance3D.new() mi.mesh = BoxMesh.new() mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF # 模拟被 world OFF 波及 root.add_child(mi) CharShadow.attach(root, 1.4) _ck(mi.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_ON, "CharShadow 把 mesh cast_shadow 拉回 ON") var blob := root.get_node_or_null("BlobShadow") _ck(blob != null and blob is Decal, "CharShadow 加了 BlobShadow Decal") _ck(blob != null and blob.texture_albedo != null, "Decal 有径向渐变贴图") _ck(blob != null and absf(blob.size.x - 1.4) < 0.01, "Decal 足印尺寸 = footprint") CharShadow.attach(root, 1.4) # 幂等 var n_blob := 0 for c in root.get_children(): if c.name == "BlobShadow": n_blob += 1 _ck(n_blob == 1, "CharShadow.attach 幂等,不重复加 Decal") # ---- 6. player_controller._ray_pick_t:射线 vs 网格世界 AABB ---- var pc = PlayerCtl.new() get_root().add_child(pc) var ent := Node3D.new() ent.position = Vector3(0, 0, -10) get_root().add_child(ent) var emi := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = Vector3(2, 2, 2) emi.mesh = bm emi.position = Vector3(0, 1, 0) # 盒中心 (0,1,-10) ent.add_child(emi) await process_frame # 从原点朝 -Z 打,正中盒子 var t_hit: float = pc._ray_pick_t(Vector3.ZERO, Vector3(0, 0, -1), ent) _ck(t_hit > 8.0 and t_hit < 10.0, "_ray_pick_t 命中:进入 t≈9 (got %.2f)" % t_hit) # 朝 +X 打,不该命中 var t_miss: float = pc._ray_pick_t(Vector3.ZERO, Vector3(1, 0, 0), ent) _ck(t_miss < 0.0, "_ray_pick_t 未命中返回 -1 (got %.2f)" % t_miss) # 擦边未命中(偏 5m) var t_edge: float = pc._ray_pick_t(Vector3(5, 1, 0), Vector3(0, 0, -1), ent) _ck(t_edge < 0.0, "_ray_pick_t 偏 5m 不命中") # 无网格 → 回退胶囊近似(PICK_RADIUS 内命中) var ent2 := Node3D.new() ent2.position = Vector3(0, 0, -6) get_root().add_child(ent2) var t_cap: float = pc._ray_pick_t(Vector3.ZERO, Vector3(0, 0, -1), ent2) _ck(t_cap > 0.0, "_ray_pick_t 无网格 → 胶囊近似仍能命中 (got %.2f)" % t_cap) # ---- 3. game_scene._on_local_motion_event 分派 ---- var GS = load("res://game_scene.gd") var gs = GS.new() get_root().add_child(gs) # 桩:cam.shake / fx.spawn_at / _audio.play_at / ui var calls := {"shake": 0, "spawn": [], "sound": []} var fake_cam := Camera3D.new() fake_cam.set_script(_inline_stub(""" extends Camera3D var log func shake(s, d=8.0): log["shake"] += 1 """)) fake_cam.set("log", calls) var fake_fx = RefCounted.new() fake_fx.set_script(_inline_stub(""" extends RefCounted var log func spawn_at(n, p, gp, o=true): log["spawn"].append(n); return null """)) fake_fx.set("log", calls) var fake_audio := Node.new() fake_audio.set_script(_inline_stub(""" extends Node var log func play_at(rel, pos, parent=null): log["sound"].append(rel) """)) fake_audio.set("log", calls) gs.cam = fake_cam gs.fx = fake_fx gs._audio = fake_audio gs.player = Node3D.new() gs._mount = Node3D.new() var fake_client := Node.new() fake_client.set_script(_inline_stub(""" extends Node var shots := [] func shoot(skill): shots.append(skill); return true """)) get_root().add_child(fake_client) gs.client = fake_client get_root().add_child(gs._mount) gs.ui = CanvasLayer.new() get_root().add_child(gs.ui) gs.hud = Node.new() gs._on_local_motion_event(5, "", "sfx/hit.wav", Vector3.ZERO) _ck(calls["sound"] == ["sfx/hit.wav"], "motion_event: sound → play_at") gs._on_local_motion_event(1, "effect/sword_trail.mse", "", Vector3(0, 100, 0)) _ck(calls["spawn"] == ["sword_trail"], "motion_event: effect → fx.spawn_at(basename)") gs._on_local_motion_event(2, "", "", Vector3.ZERO) # SCREEN_WAVING _ck(calls["shake"] == 1, "motion_event: type 2 → cam.shake") gs._on_local_motion_event(3, "", "", Vector3.ZERO) # SCREEN_FLASHING await process_frame _ck(gs.ui.get_node_or_null("ColorRect") != null or gs._flash_rect != null, "motion_event: type 3 → 屏闪 ColorRect") gs._pending_shoot_skill = 77 gs._on_local_motion_event(6, "", "", Vector3.ZERO) _ck(fake_client.shots == [77], "motion_event: FLY → sends pending CG_SHOOT once") gs._on_local_motion_event(6, "", "", Vector3.ZERO) _ck(fake_client.shots == [77], "motion_event: FLY clears pending shoot") gs.queue_free() func _inline_stub(src: String) -> GDScript: var s := GDScript.new() s.source_code = src s.reload() return s