extends Node2D ## Строгий headless-тест механик Этапа 1. ## Сцена: основной пол 800x40 (центр x=0), страховочный пол ниже (y=1000). ## Лиса стартует на (0,300). var fox: CharacterBody2D var phase: int = 0 var pt: float = 0.0 # время внутри фазы var ground_y: float = 0.0 var min_y: float = 99999.0 var results: Array = [] # служебные флаги var pressed_jump: bool = false var airborne_at: float = -1.0 var left_ledge: bool = false func _ready() -> void: fox = $FoxInstance func _reset_phase() -> void: pt = 0.0 min_y = 99999.0 pressed_jump = false left_ledge = false airborne_at = -1.0 func _physics_process(delta: float) -> void: pt += delta match phase: 0: # ждём приземления if fox.is_on_floor() and pt > 0.2: ground_y = fox.position.y results.append("landing: y=%.1f [OK]" % ground_y) phase = 1 _reset_phase() 1: # ДЛИННЫЙ прыжок (держим кнопку) if pt < 0.02: Input.action_press("jump") min_y = minf(min_y, fox.position.y) if pt > 1.4: Input.action_release("jump") results.append("long jump rise: %.1f px (теория v^2/2g = 103.3)" % (ground_y - min_y)) phase = 2 _reset_phase() 2: # ждём приземления if fox.is_on_floor() and pt > 0.3: phase = 3 _reset_phase() 3: # КОРОТКИЙ прыжок (отпускаем через 0.05с) if pt < 0.02: Input.action_press("jump") if pt > 0.05 and not pressed_jump: Input.action_release("jump") pressed_jump = true min_y = minf(min_y, fox.position.y) if pt > 1.2: Input.action_release("jump") results.append("short jump rise: %.1f px (должен быть заметно меньше длинного)" % (ground_y - min_y)) phase = 4 _reset_phase() 4: # ждём приземления if fox.is_on_floor() and pt > 0.3: phase = 5 _reset_phase() 5: # COYOTE TIME: бежим вправо, после схода прыгаем через ~0.05с Input.action_press("move_right") Input.action_press("run") if not fox.is_on_floor() and not left_ledge: left_ledge = true airborne_at = 0.0 if left_ledge: airborne_at += delta if airborne_at >= 0.05 and not pressed_jump: Input.action_press("jump") pressed_jump = true results.append("coyote: jump нажат через %.3fс после схода" % airborne_at) phase = 6 _reset_phase() if pt > 3.0: results.append("coyote: FAIL — не дошёл до края") phase = 8 _reset_phase() 6: # проверяем результат coyote-прыжка if pt > 0.12: var ok := fox.velocity.y < -100.0 results.append("coyote jump: %s (vel.y=%.0f)" % ["PASS" if ok else "FAIL", fox.velocity.y]) Input.action_release("jump") phase = 7 _reset_phase() 7: # JUMP BUFFER (детерминированно): один телепорт в воздух, затем jump за ~0.07с до земли Input.action_release("move_left") Input.action_release("move_right") Input.action_release("run") if not fox.has_meta("tp"): fox.set_meta("tp", true) fox.position = Vector2(0.0, ground_y - 120.0) fox.velocity = Vector2.ZERO results.append("buffer: лиса телепортирована на y=%.1f, ждём падения" % fox.position.y) elif not pressed_jump: var dist: float = maxf(ground_y - fox.position.y, 0.0) var v: float = maxf(fox.velocity.y, 0.0) var ttl: float = (-v + sqrt(v * v + 2.0 * 980.0 * dist)) / 980.0 if dist > 0.0 and ttl <= 0.07: Input.action_press("jump") pressed_jump = true results.append("buffer: jump нажат за %.3fс до земли (y=%.1f, vel.y=%.0f)" % [ttl, fox.position.y, v]) if pressed_jump: if fox.velocity.y < -100.0: results.append("buffer jump: PASS (vel.y=%.0f — прыжок сработал при касании)" % fox.velocity.y) Input.action_release("jump") phase = 8 _reset_phase() elif pt > 3.0: results.append("buffer jump: FAIL (vel.y=%.0f, on_floor=%s)" % [fox.velocity.y, fox.is_on_floor()]) Input.action_release("jump") phase = 8 _reset_phase() 8: Input.action_release("move_left") Input.action_release("move_right") Input.action_release("run") Input.action_release("jump") if pt > 0.3: print("") print("========== RESULTS: STAGE 1 PHYSICS ==========") for r in results: print(" * ", r) print("==============================================") get_tree().quit(0)