Harden RV interior exits (#127)

This commit is contained in:
Alexander Sellite 2026-09-02 09:52:11 -04:00
parent 62af5e7086
commit 6eacd17846
4 changed files with 319 additions and 26 deletions

View file

@ -81,20 +81,62 @@ func _run_host() -> void:
session.get_local_peer_id(), upgrade_result_id, true
)
assert(rv_exits[0].x < rv_exits[1].x)
var interior_anchor: Vector3 = home_world.get_interior_anchor(
host_fingerprint
)
for exit_position: Vector3 in rv_exits:
host_avatar.global_position = (
exit_position
+ home_world.get_interior_exit_direction_near(
var exit_direction: Vector3 = (
home_world.get_interior_exit_direction_near(
host_fingerprint, exit_position
) * 0.05
)
)
assert(home_world.has_avatar_crossed_interior_exit_threshold(
var airborne_inside: Vector3 = (
exit_position - exit_direction * 0.8 + Vector3.UP * 0.55
)
var airborne_beyond_trigger: Vector3 = (
exit_position
+ exit_direction
* (HomeWorldService.INTERIOR_EXIT_TRIGGER_DEPTH + 0.8)
+ Vector3.UP * 0.75
)
host_avatar.global_position = airborne_beyond_trigger
# The endpoint is deliberately beyond the narrow legacy trigger strip.
# Only swept crossing detection can recognize this airborne frame.
assert(not home_world.has_avatar_crossed_interior_exit_threshold(
host_avatar, host_fingerprint
))
assert(home_world.has_avatar_crossed_interior_exit_threshold(
host_avatar, host_fingerprint, airborne_inside
))
var wall_crossing: Vector3 = airborne_beyond_trigger + Vector3(
0.0,
0.0,
HomeWorldService.INTERIOR_EXIT_DOORWAY_HALF_WIDTH + 0.5,
)
host_avatar.global_position = wall_crossing
assert(not home_world.has_avatar_crossed_interior_exit_threshold(
host_avatar,
host_fingerprint,
airborne_inside + Vector3(
0.0,
0.0,
HomeWorldService.INTERIOR_EXIT_DOORWAY_HALF_WIDTH + 0.5,
),
))
host_avatar.global_position = rv_exits[0] + Vector3(0.0, 0.0, 1.1)
assert(not home_world.has_avatar_crossed_interior_exit_threshold(
host_avatar, host_fingerprint
))
host_avatar.global_position = interior_anchor + Vector3(
0.0, -HomeWorldService.INTERIOR_VOID_FALL_DEPTH - 0.1, 0.0
)
assert(home_world.is_avatar_below_interior_floor(
host_avatar, host_fingerprint
))
host_avatar.global_position = interior_anchor
assert(not home_world.is_avatar_below_interior_floor(
host_avatar, host_fingerprint
))
var presentations: Dictionary = home_world.get(
"_presentations_by_fingerprint"
)
@ -111,9 +153,79 @@ func _run_host() -> void:
var wall_shape := wall_collision.get_child(0) as CollisionShape3D
assert(wall_shape != null)
assert((wall_shape.shape as ConcavePolygonShape3D).backface_collision)
host_avatar.global_position = host_spawn.origin
for door_index: int in rv_exits.size():
var door_name: String = (
"WallWestDoor" if door_index == 0 else "WallEastDoor"
)
var door_mesh := host_interior.find_child(
door_name, true, false
) as MeshInstance3D
assert(door_mesh != null and door_mesh.mesh != null)
var authored_door_center: Vector3 = (
door_mesh.global_transform * door_mesh.get_aabb().get_center()
)
assert(absf(authored_door_center.z - rv_exits[door_index].z) < 0.05)
var rv_host_spawn: Transform3D = home_world.get_exterior_spawn_transform(
host_fingerprint
)
host_avatar.global_position = rv_host_spawn.origin
assert(network_home.request_enter(host_fingerprint))
await physics_frame
assert(await _wait_for_home_entry(
main, network_home, host_fingerprint
))
var water_recovery := main.get_node(
"%WaterRecovery"
) as WaterRecoveryController
assert(not water_recovery.is_recovery_enabled())
for exit_position: Vector3 in rv_exits:
var exit_direction: Vector3 = (
home_world.get_interior_exit_direction_near(
host_fingerprint, exit_position
)
)
var airborne_inside: Vector3 = (
exit_position - exit_direction * 0.8 + Vector3.UP * 0.55
)
var airborne_outside: Vector3 = (
exit_position
+ exit_direction
* (HomeWorldService.INTERIOR_EXIT_TRIGGER_DEPTH + 0.8)
+ Vector3.UP * 0.75
)
main.set("_home_exit_tracked_owner", host_fingerprint)
main.set("_home_exit_previous_position", airborne_inside)
host_avatar.global_position = airborne_outside
host_avatar.velocity = exit_direction * host_avatar.sprint_speed
host_avatar.velocity.y = 2.0
main.call("_maybe_begin_home_exit_transition")
assert(bool(main.get("_home_exit_transition_active")))
assert(not water_recovery.is_recovery_active())
assert(await _wait_for_home_exit(main, network_home))
assert(water_recovery.is_recovery_enabled())
# Fade-in gives gravity time to settle the capsule from the authored
# 0.25 m spawn lift. The horizontal authoritative target remains exact.
assert(_same_door_horizontal_position(
host_avatar.global_position, rv_host_spawn.origin
))
assert(absf(
host_avatar.global_position.y - rv_host_spawn.origin.y
) < 0.6)
assert(network_home.request_enter(host_fingerprint))
assert(await _wait_for_home_entry(
main, network_home, host_fingerprint
))
assert(not water_recovery.is_recovery_enabled())
# If imported collision ever regresses, falling beneath an active room uses
# the same authoritative home exit instead of starting world-water recovery.
host_avatar.global_position = interior_anchor + Vector3(
0.0, -HomeWorldService.INTERIOR_VOID_FALL_DEPTH - 0.1, 0.0
)
main.call("_maybe_begin_home_exit_transition")
assert(bool(main.get("_home_exit_transition_active")))
assert(not water_recovery.is_recovery_active())
main.call("_cancel_home_exit_transition", true)
host_avatar.global_position = interior_anchor
main.call("_reset_home_exit_position_tracking", host_fingerprint)
var remote_peer_id: int = await _wait_for_remote_peer(session)
assert(remote_peer_id > 1)
@ -153,6 +265,7 @@ func _run_host() -> void:
):
await process_frame
assert(network_home.get_local_home_owner_fingerprint().is_empty())
assert(water_recovery.is_recovery_enabled())
var visibility_deadline: int = Time.get_ticks_msec() + 1000
while (
Time.get_ticks_msec() < visibility_deadline
@ -263,6 +376,37 @@ func _wait_for_home(
return false
func _wait_for_home_entry(
main: Node,
network_home: NetworkHomeService,
fingerprint: String,
) -> bool:
var deadline: int = Time.get_ticks_msec() + 5000
while Time.get_ticks_msec() < deadline:
await process_frame
if (
network_home.get_local_home_owner_fingerprint() == fingerprint
and not bool(main.get("_home_entry_transition_active"))
):
return true
return false
func _wait_for_home_exit(
main: Node,
network_home: NetworkHomeService,
) -> bool:
var deadline: int = Time.get_ticks_msec() + 5000
while Time.get_ticks_msec() < deadline:
await process_frame
if (
network_home.get_local_home_owner_fingerprint().is_empty()
and not bool(main.get("_home_exit_transition_active"))
):
return true
return false
func _faces_away_from_home(
avatar: Player,
home_world: HomeWorldService,
@ -292,12 +436,17 @@ func _wait_for_remote_peer(session: NetworkSession) -> int:
func _same_door_position(first: Vector3, second: Vector3) -> bool:
return (
Vector2(first.x, first.z).distance_to(Vector2(second.x, second.z))
< 0.08
_same_door_horizontal_position(first, second)
and absf(first.y - second.y) < 0.3
)
func _same_door_horizontal_position(first: Vector3, second: Vector3) -> bool:
return Vector2(first.x, first.z).distance_to(
Vector2(second.x, second.z)
) < 0.08
func _cleanup(main: Node, session: NetworkSession) -> void:
session.disconnect_session("")
main.queue_free()