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
+195
View File
@@ -0,0 +1,195 @@
extends SceneTree
const DriverScript := preload("res://scenes/components/frame_collision_driver.gd")
const EmitterScript := preload("res://scenes/components/damage_emitter.gd")
const ReceiverScript := preload("res://scenes/components/damage_receiver.gd")
const ActionControllerScript := preload("res://scenes/components/action_controller.gd")
const ActionDataScript := preload("res://resources/action_data.gd")
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
var actor := _build_actor()
root.add_child(actor)
await process_frame
var driver: Node = actor.get_node("FrameCollisionDriver")
var receiver := actor.get_node("DamageReceiver") as Area2D
var receiver_shape := receiver.get_node("CollisionShape2D") as CollisionShape2D
var emitter := actor.get_node("DamageEmitter") as Area2D
var emitter_shape := emitter.get_node("CollisionShape2D") as CollisionShape2D
var sprite := actor.get_node("Visual/CharacterSprite") as Sprite2D
var controller: Node = actor.get_node("ActionController")
# --- hurtbox follows the displayed frame's opaque bounds ---
sprite.frame = 0
driver.call("refresh_now")
var body_size := (receiver_shape.shape as RectangleShape2D).size
_expect_float(body_size.x, 24.0, "Frame 0 hurtbox width should match the small pose")
_expect_float(body_size.y, 40.0, "Frame 0 hurtbox height should match the pose")
_expect_vector(receiver_shape.position, Vector2(0.0, -20.0), "Frame 0 hurtbox should centre on the pose")
sprite.frame = 2
driver.call("refresh_now")
var extended_size := (receiver_shape.shape as RectangleShape2D).size
_expect_float(extended_size.x, 44.0, "Frame 2 hurtbox should widen with the extended pose")
_expect_bool(extended_size.x > body_size.x, true, "Damage-receive matrix must change per frame")
# --- hitbox only monitors on reaching ACTIVE frames ---
var action: Resource = ActionDataScript.new()
action.set("hit_type", &"melee")
action.set("range", 40.0)
emitter.call("configure_hit", action, {"label": "perfect"})
controller.set("phase", ActionControllerScript.Phase.ACTIVE)
sprite.frame = 0
driver.call("refresh_now")
_expect_bool(emitter.monitoring, false, "Windup frame (no forward reach) must not send damage")
sprite.frame = 2
driver.call("refresh_now")
_expect_bool(emitter.monitoring, true, "Weapon-extended frame must send damage")
_expect_bool(emitter_shape.position.x < 0.0, true, "Facing left, the hit shape extends left")
_expect_bool((emitter_shape.shape as RectangleShape2D).size.x >= 30.0, true, "Hit reach should cover max(pose reach, action range)")
# recovery: leaving ACTIVE closes the window on the same frame
controller.set("phase", ActionControllerScript.Phase.RECOVERY)
driver.call("refresh_now")
_expect_bool(emitter.monitoring, false, "Leaving ACTIVE must close the hit window")
controller.set("phase", ActionControllerScript.Phase.ACTIVE)
# --- mirroring follows heading/Visual flip ---
actor.set("heading", Vector2.RIGHT)
(actor.get_node("Visual") as Node2D).scale.x = -2.0
driver.call("refresh_now")
_expect_bool(emitter.monitoring, true, "Mirrored reach frame still sends damage")
_expect_bool(emitter_shape.position.x > 0.0, true, "Facing right, the hit shape extends right")
# --- body matrix is re-asserted every tick ---
actor.collision_layer = 5
actor.collision_mask = 99
driver.call("refresh_now")
_expect_int(actor.collision_layer, 32, "Body collision layer must be re-applied per frame")
_expect_int(actor.collision_mask, 65, "Body collision mask must be re-applied per frame")
# --- one hit per swing even with per-frame windows ---
var target := Area2D.new()
target.add_to_group("damage_receivers")
root.add_child(target)
emitter.call("_on_area_entered", target)
emitter.call("_on_area_entered", target)
_expect_bool(emitter.call("has_already_hit", target), true, "Emitter should remember receivers hit this swing")
emitter.call("clear_hit")
_expect_bool(emitter.call("has_already_hit", target), false, "clear_hit should reset the per-swing memory")
target.free()
actor.free()
_finish()
func _build_actor() -> CharacterBody2D:
var actor_script := GDScript.new()
actor_script.source_code = "extends CharacterBody2D\nvar heading := Vector2.LEFT\n"
actor_script.reload()
var actor := CharacterBody2D.new()
actor.set_script(actor_script)
actor.collision_layer = 32
actor.collision_mask = 65
var visual := Node2D.new()
visual.name = "Visual"
visual.scale = Vector2(2, 2)
actor.add_child(visual)
var sprite := Sprite2D.new()
sprite.name = "CharacterSprite"
sprite.centered = false
sprite.offset = Vector2(-16, -32)
sprite.hframes = 4
sprite.vframes = 1
sprite.texture = _build_sheet()
visual.add_child(sprite)
var emitter := Area2D.new()
emitter.name = "DamageEmitter"
emitter.set_script(EmitterScript)
emitter.collision_layer = 8
emitter.collision_mask = 4
emitter.monitoring = false
var emitter_shape := CollisionShape2D.new()
emitter_shape.name = "CollisionShape2D"
emitter_shape.shape = RectangleShape2D.new()
emitter.add_child(emitter_shape)
actor.add_child(emitter)
var receiver := Area2D.new()
receiver.name = "DamageReceiver"
receiver.set_script(ReceiverScript)
receiver.collision_layer = 2
receiver.collision_mask = 16
var receiver_shape := CollisionShape2D.new()
receiver_shape.name = "CollisionShape2D"
receiver_shape.shape = RectangleShape2D.new()
receiver.add_child(receiver_shape)
actor.add_child(receiver)
var controller_script := GDScript.new()
controller_script.source_code = "extends Node\nvar phase := 0\n"
controller_script.reload()
var controller := Node.new()
controller.name = "ActionController"
controller.set_script(controller_script)
actor.add_child(controller)
var driver := Node.new()
driver.name = "FrameCollisionDriver"
driver.set_script(DriverScript)
driver.set("hurt_margin", Vector2.ZERO)
actor.add_child(driver)
return actor
func _build_sheet() -> ImageTexture:
# 4 frames of 32x32. Frame 0: compact body (x 10..21). Frame 2: weapon
# extended to the native-left edge (x 0..21). Frames 1/3 stay empty.
var image := Image.create(128, 32, false, Image.FORMAT_RGBA8)
image.fill(Color(0, 0, 0, 0))
for y: int in range(12, 32):
for x: int in range(10, 22):
image.set_pixel(x, y, Color.WHITE)
for x: int in range(64, 86):
image.set_pixel(x, y, Color.WHITE)
return ImageTexture.create_from_image(image)
func _expect_bool(actual: bool, expected: bool, label: String) -> void:
if actual != expected:
failures.append("%s: expected %s, got %s" % [label, expected, actual])
func _expect_int(actual: int, expected: int, label: String) -> void:
if actual != expected:
failures.append("%s: expected %d, got %d" % [label, expected, actual])
func _expect_float(actual: float, expected: float, label: String) -> void:
if absf(actual - expected) > 0.5:
failures.append("%s: expected %.2f, got %.2f" % [label, expected, actual])
func _expect_vector(actual: Vector2, expected: Vector2, label: String) -> void:
if actual.distance_to(expected) > 0.75:
failures.append("%s: expected %s, got %s" % [label, expected, actual])
func _finish() -> void:
if failures.is_empty():
print("PASS frame collision driver")
quit(0)
else:
for failure: String in failures:
push_error(failure)
quit(1)