extends SceneTree
## DOES WALKING DEGRADE WITH HORIZONTAL DISTANCE FROM THE ORIGIN?
##
## godot --headless --path . --script res://scenes/test/far_coord_walk_gate.gd
##
## The diffusion map bakes its heightfield at world x 38370..69150,
## z 23010..53790, so the player walks 40-70 km from the origin. The erosion map
## keeps you within a few km. Measured travel efficiency: 6% there, ~95% here.
## The scenes are otherwise identical, and the SAME scene with a broken script
## and a completely different generator is also hard to walk on -- so the cause
## is not the terrain source.
##
## float32 at 53,753 m has an ULP of 3.91 mm. CharacterBody3D's safe_margin is
## 1 mm. A margin finer than the coordinate resolution cannot mean anything, and
## the penetration measured in play sat at 1.88 mm -- about half an ULP.
##
## hyper-games #47 recorded "horizontal distance does not break physics -- clean
## at 100 km", measured with large_coord_physics_gate. This asks a different
## question: not whether a cast lands, but whether a capsule DRIVEN ALONG a
## triangulated slope keeps its speed. Those can differ, and the play data says
## they do.
##
## Exit 0 if efficiency at the far offsets stays near the offset-0 control.
const RADIUS := 0.3
const HEIGHT := 1.58
const SLOPE_DEG := 15.0
const STEPS := 240
const DT := 1.0 / 60.0
const SPEED := 8.0
const GRAVITY := 9.8
func _initialize() -> void:
# THE CONTACT NORMAL, whose right answer is known exactly.
#
# The first version of this gate simulated walking and its control failed at
# offset 0 (60.8%), which makes every other row unusable -- a hand-rolled
# slide loop is a bad walker, and a harness that cannot do the thing at the
# origin cannot report on doing it far away.
#
# This asks something with an exact answer instead: drop the shipping capsule
# onto a 15.00 degree slope and read the normal the physics server reports.
# It should be 15.00 degrees at every distance. Any deviation is contact
# resolution failing, and needs no model of walking to interpret.
print("capsule r=%.2f h=%.2f dropped onto a %.2f deg trimesh slope" % [RADIUS, HEIGHT, SLOPE_DEG])
print("the reported contact normal should read %.2f deg at EVERY offset" % SLOPE_DEG)
print()
print(" offset (m) | ULP (m) | contact normal | error (deg) | depth (m)")
var worst_err := 0.0
var worst_off := 0.0
var base_err := -1.0
for off in [0.0, 1000.0, 5000.0, 20000.0, 38400.0, 53750.0, 100000.0]:
var r := _probe_normal(off)
if not bool(r["hit"]):
print(" %10.0f | %8.5f | NO CONTACT AT ALL" % [off, r["ulp"]])
continue
var err: float = absf(float(r["deg"]) - SLOPE_DEG)
if base_err < 0.0:
base_err = err
if err > worst_err:
worst_err = err
worst_off = off
print(" %10.0f | %8.5f | %13.3f | %10.3f | %.6f"
% [off, r["ulp"], r["deg"], err, r["depth"]])
print()
# CONTROL: at the origin the answer must be right, or nothing below it counts.
var ok := base_err >= 0.0 and base_err < 0.05
print("CONTROL at offset 0: error %.4f deg -> %s"
% [base_err, "PASS" if ok else "FAIL — the harness is wrong, ignore every row"])
if ok:
print("WORST: %.3f deg error at %.0f m" % [worst_err, worst_off])
if worst_err > 1.0:
print("=> CONTACT RESOLUTION DEGRADES WITH DISTANCE FROM THE ORIGIN.")
print(" No terrain generator, no heightfield, no GPU: a flat analytic")
print(" slope and the shipping capsule.")
else:
print("=> The contact normal holds at every distance. Coordinate magnitude")
print(" does NOT explain the play measurement, and I should stop saying it does.")
quit(0 if ok else 2)
## Drop the capsule straight down onto the slope and report the contact.
func _probe_normal(off: float) -> Dictionary:
var world := World3D.new()
var shape: RID = PhysicsServer3D.concave_polygon_shape_create()
PhysicsServer3D.shape_set_data(shape, {"faces": _slope_faces(off)})
var ground: RID = PhysicsServer3D.body_create()
PhysicsServer3D.body_set_mode(ground, PhysicsServer3D.BODY_MODE_STATIC)
PhysicsServer3D.body_add_shape(ground, shape)
PhysicsServer3D.body_set_space(ground, world.space)
var cap := CapsuleShape3D.new()
cap.radius = RADIUS
cap.height = HEIGHT
var body: RID = PhysicsServer3D.body_create()
PhysicsServer3D.body_set_mode(body, PhysicsServer3D.BODY_MODE_KINEMATIC)
PhysicsServer3D.body_add_shape(body, cap.get_rid())
PhysicsServer3D.body_set_space(body, world.space)
# Start 2 m above the slope at its centre, fall 4 m.
var start := Vector3(off, 2.0, off)
var params := PhysicsTestMotionParameters3D.new()
params.from = Transform3D(Basis(), start)
params.motion = Vector3(0, -4.0, 0)
PhysicsServer3D.body_set_state(body, PhysicsServer3D.BODY_STATE_TRANSFORM, params.from)
var res := PhysicsTestMotionResult3D.new()
var hit: bool = PhysicsServer3D.body_test_motion(body, params, res)
var out := {"hit": hit, "deg": 0.0, "depth": 0.0, "ulp": 0.0}
if hit:
var n: Vector3 = res.get_collision_normal()
out["deg"] = rad_to_deg(acos(clampf(n.dot(Vector3.UP), -1.0, 1.0)))
out["depth"] = res.get_collision_depth()
if off > 0.0:
out["ulp"] = (off * 1.0000001192092896) - off
PhysicsServer3D.free_rid(body)
PhysicsServer3D.free_rid(ground)
PhysicsServer3D.free_rid(shape)
return out
## A triangulated slope patch, 60 x 60 m, centred at (off, 0, off), meshed at
## 1 m like the terrain's LOD-0 voxels so the triangle scale matches.
func _slope_faces(off: float) -> PackedVector3Array:
var f := PackedVector3Array()
var t := tan(deg_to_rad(SLOPE_DEG))
var n := 60
for i in range(n):
for j in range(n):
var x0 := off - 30.0 + float(i)
var z0 := off - 30.0 + float(j)
var p00 := Vector3(x0, (x0 - off) * t, z0)
var p10 := Vector3(x0 + 1.0, (x0 + 1.0 - off) * t, z0)
var p01 := Vector3(x0, (x0 - off) * t, z0 + 1.0)
var p11 := Vector3(x0 + 1.0, (x0 + 1.0 - off) * t, z0 + 1.0)
# Winding measured by trimesh_winding_gate: a standable floor has
# (b-a)x(c-a) pointing DOWN.
f.append_array([p00, p10, p11])
f.append_array([p00, p11, p01])
return f
func _walk(off: float) -> Dictionary:
var world := World3D.new()
var shape: RID = PhysicsServer3D.concave_polygon_shape_create()
PhysicsServer3D.shape_set_data(shape, {"faces": _slope_faces(off)})
var ground: RID = PhysicsServer3D.body_create()
PhysicsServer3D.body_set_mode(ground, PhysicsServer3D.BODY_MODE_STATIC)
PhysicsServer3D.body_add_shape(ground, shape)
PhysicsServer3D.body_set_space(ground, world.space)
var cap := CapsuleShape3D.new()
cap.radius = RADIUS
cap.height = HEIGHT
var body: RID = PhysicsServer3D.body_create()
PhysicsServer3D.body_set_mode(body, PhysicsServer3D.BODY_MODE_KINEMATIC)
PhysicsServer3D.body_add_shape(body, cap.get_rid())
PhysicsServer3D.body_set_space(body, world.space)
var pos := Vector3(off - 20.0, (off - 20.0 - off) * tan(deg_to_rad(SLOPE_DEG)) + HEIGHT, off)
var want_total := 0.0
var got_total := 0.0
var contacts := 0.0
var vy := 0.0
for s in range(STEPS):
# Gravity, then a horizontal push along +X. A miniature move_and_slide:
# attempt the motion, and on a hit slide the remainder along the plane.
vy -= GRAVITY * DT
var motion := Vector3(SPEED * DT, vy * DT, 0.0)
var want := absf(SPEED * DT)
var before := pos
for _slide in range(4):
var params := PhysicsTestMotionParameters3D.new()
params.from = Transform3D(Basis(), pos)
params.motion = motion
var res := PhysicsTestMotionResult3D.new()
var hit: bool = PhysicsServer3D.body_test_motion(body, params, res)
if not hit:
pos += motion
break
contacts += 1.0
pos += motion * res.get_collision_safe_fraction()
var n: Vector3 = res.get_collision_normal()
if n.dot(Vector3.UP) > 0.5:
vy = 0.0
var remainder: Vector3 = motion * (1.0 - res.get_collision_safe_fraction())
motion = remainder.slide(n)
if motion.length_squared() < 1e-12:
break
want_total += want
got_total += (pos - before).x
PhysicsServer3D.body_set_state(body, PhysicsServer3D.BODY_STATE_TRANSFORM,
Transform3D(Basis(), pos))
PhysicsServer3D.free_rid(body)
PhysicsServer3D.free_rid(ground)
PhysicsServer3D.free_rid(shape)
var ulp := 0.0
if off > 0.0:
var fo := float(off)
ulp = (fo * 1.0000001192092896) - fo # one float32 step at that magnitude
return {
"got": got_total, "want": want_total,
"eff": got_total / maxf(want_total, 1e-9),
"contacts": contacts / float(STEPS),
"ulp": ulp,
}