Initial project sync

This commit is contained in:
wxm
2026-07-06 22:55:14 -07:00
commit 8cca00d0da
1066 changed files with 58585 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
extends SceneTree
## 2026-07-06 策划契约:冲刺穿人机制保留,但其余时间玩家不得与敌人重叠。
## 旧行为:dash_through 落点嵌在敌人体内时,恢复碰撞的逻辑"干等分离"——
## 嵌着就无限期保持幽灵态,玩家可长时间站在小怪/Boss 身体里。
## 新行为:嵌入时沿冲刺方向每物理帧温和推出(保留"从远侧穿出"手感),
## 推清后恢复身体碰撞。
const ENEMY_BODY_BIT := 1 << 6
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
root.size = Vector2i(1152, 648)
var settings := root.get_node_or_null("GameSettings")
if settings != null:
settings.call("set_difficulty", &"easy")
await _check_embedded_dash_gets_separated()
await _check_clean_dash_restores_immediately()
_finish()
func _make_actors() -> Dictionary:
var player := (load("res://scenes/characters/player.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(player)
var minion := (load("res://scenes/enemies/minion.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(minion)
var behavior := minion.get_node_or_null("MinionBehavior")
if behavior != null:
behavior.set("enabled", false)
minion.set("approach_direction", 0.0)
return {"player": player, "minion": minion}
func _dash_action() -> Resource:
var script: Script = load("res://resources/action_data.gd")
var action: Resource = script.new()
var action_tags: Array[StringName] = [&"dash_through"]
action.set("id", &"test_dash_embed")
action.set("action_tags", action_tags)
action.set("move_mult_x", 1.0)
action.set("startup_beats", 0.25)
action.set("active_beats", 0.25)
action.set("recovery_beats", 0.25)
return action
func _check_embedded_dash_gets_separated() -> void:
var actors := _make_actors()
var player := actors["player"] as CharacterBody2D
var minion := actors["minion"] as CharacterBody2D
await process_frame
player.global_position = Vector2(500.0, 300.0)
# 冲刺时长 0.75 拍 × 0.5s × 220px/s ≈ 82px:小怪摆在 80px 处,落点必然嵌入。
minion.global_position = Vector2(580.0, 300.0)
await physics_frame
var motion: Node = player.get_node("MotionExecutor")
motion.call("execute", _dash_action(), Vector2.RIGHT, 0.5, 220.0)
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) == 0, "冲刺期间保持幽灵态(机制保留)")
# 裸场景无 handle_movement 驱动,手动按真实链路推进:tick → move_and_slide。
var finished := false
for _index: int in range(90):
player.velocity = motion.call("tick", 1.0 / 60.0)
player.move_and_slide()
await physics_frame
if not bool(motion.get("active")):
finished = true
break
_expect(finished, "冲刺应在 90 帧内结束")
# 新契约:嵌入不再无限期保持幽灵态——有限帧内推出并恢复碰撞。
var separated := false
for _index: int in range(45):
await physics_frame
if not bool(motion.call("is_ghosting")):
separated = true
break
_expect(separated, "落点嵌入后应在 45 帧内被推出并恢复碰撞(旧行为:无限期重叠)")
if separated:
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) != 0, "分离后玩家身体碰撞恢复")
_expect(player.global_position.x > minion.global_position.x,
"应从冲刺方向的远侧穿出(不被弹回入口侧)")
var gap := absf(player.global_position.x - minion.global_position.x)
_expect(gap >= 18.0, "分离后身体不再重叠(间距 %.1f px ≥ 半宽和)" % gap)
player.free()
minion.free()
func _check_clean_dash_restores_immediately() -> void:
# 落点干净(前方无敌人)时行为与旧版一致:结束即恢复。
var player := (load("res://scenes/characters/player.tscn") as PackedScene).instantiate() as CharacterBody2D
root.add_child(player)
await process_frame
player.global_position = Vector2(500.0, 300.0)
var motion: Node = player.get_node("MotionExecutor")
motion.call("execute", _dash_action(), Vector2.RIGHT, 0.5, 220.0)
for _index: int in range(90):
player.velocity = motion.call("tick", 1.0 / 60.0)
player.move_and_slide()
await physics_frame
if not bool(motion.get("active")):
break
await physics_frame
await physics_frame
_expect(not bool(motion.call("is_ghosting")), "干净落点:冲刺结束立即恢复碰撞")
_expect((int(player.collision_mask) & ENEMY_BODY_BIT) != 0, "干净落点:敌人身体位复位")
player.free()
func _expect(condition: bool, label: String) -> void:
if not condition:
failures.append(label)
func _finish() -> void:
paused = false
if failures.is_empty():
print("PASS dash overlap separation")
quit(0)
else:
for failure: String in failures:
push_error(failure)
quit(1)