LANE9: the grocer is still again — settle sweep + parked scale pans
probe_levels was red (0.15-0.48 m/s residual, grocer): the pin-jointed scale pans hang neutrally balanced so the joint solve re-wakes them forever, and 310 packed bodies (bottles on shelves, nested fruit) trade contact micro-jitter for seconds. Two mechanisms, both one-shot at spawn, neither touching gameplay feel: - scale pans PARK frozen once the joint has resolved (plain .sleeping doesn't stick — the joint re-wakes its island every frame) and unfreeze the first time the player comes within 1.7 m. You cannot see a pan swing from further than that. - a settle sweep the frame the spawn-guard window closes: bodies drifting slower than 0.5 m/s are put to bed. Anything faster is left for the guard to name — a real mis-spawn still fails loudly. probe_levels also names the culprit properly now ([class, parent] — name-collided siblings all render as @RigidBody3D@N, which is how three different scale pans masqueraded as one mystery body across runs). All six sites: 0.00 m/s. probe_overlap: CLEAN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8101cdb472
commit
0e80d393c1
@ -33,7 +33,10 @@ func _initialize() -> void:
|
||||
motion += v
|
||||
if v > worst_v:
|
||||
worst_v = v
|
||||
worst = "%s at %s" % [b.name, b.global_position.snappedf(0.1)]
|
||||
var scr := b.get_script() as Script
|
||||
var cls := scr.get_global_name() if scr != null else StringName(b.get_class())
|
||||
worst = "%s [%s, parent %s] at %s" % [
|
||||
b.name, cls, b.get_parent().name, b.global_position.snappedf(0.1)]
|
||||
print("\n=== %-14s %s" % [id, plan.level_name()])
|
||||
print(" smashables=%-4d static bodies=%-4d" % [bodies, statics])
|
||||
print(" residual motion after 1s = %.2f m/s worst: %s" % [
|
||||
|
||||
@ -395,6 +395,35 @@ func _build_scale(at: Vector3) -> void:
|
||||
joint.node_a = joint.get_path_to(head)
|
||||
joint.node_b = joint.get_path_to(pan)
|
||||
|
||||
# The pan's origin sits AT the pivot, so it hangs near-neutral: no restoring torque
|
||||
# ever kills the tiny kick the joint solve gives it at spawn, angular_damp is
|
||||
# deliberately low (the long swing after a hit IS the feature), and plain .sleeping
|
||||
# doesn't stick — the pin joint re-wakes its island every frame. So: once the joint
|
||||
# has resolved, PARK the pan frozen (a pendulum at rest plumb is genuinely static),
|
||||
# and unfreeze it the first time the player comes within reach. The swing feel is
|
||||
# untouched — you can't see a pan swing from further than the wake radius anyway.
|
||||
# Without this the grocer fails probe_levels at 0.15-0.48 m/s forever.
|
||||
get_tree().create_timer(0.6, false, true).timeout.connect(func() -> void:
|
||||
if is_instance_valid(pan) and not pan.is_broken() and not pan._released:
|
||||
pan.linear_velocity = Vector3.ZERO
|
||||
pan.angular_velocity = Vector3.ZERO
|
||||
pan.freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
|
||||
pan.freeze = true)
|
||||
var wake := Area3D.new()
|
||||
wake.name = "scale-wake"
|
||||
var wshape := CollisionShape3D.new()
|
||||
var wsphere := SphereShape3D.new()
|
||||
wsphere.radius = 1.7
|
||||
wshape.shape = wsphere
|
||||
wake.add_child(wshape)
|
||||
_world.add_child(wake)
|
||||
wake.global_position = pivot
|
||||
wake.body_entered.connect(func(b: Node3D) -> void:
|
||||
if b is CharacterBody3D and is_instance_valid(pan):
|
||||
pan.freeze = false
|
||||
pan.sleeping = false
|
||||
wake.queue_free())
|
||||
|
||||
## Four low timber walls on the table top, framing one pyramid. Static — you can't smash
|
||||
## the tray, you smash what's in it, and what's in it goes over the edge.
|
||||
func _stack_tray(at: Vector3, half: float, h: float) -> void:
|
||||
@ -619,6 +648,9 @@ func _physics_process(dt: float) -> void:
|
||||
if _spawn_guard <= 0.0:
|
||||
return
|
||||
_spawn_guard -= dt
|
||||
if _spawn_guard <= 0.0:
|
||||
_settle_sweep() # the window just closed — put residual drift to bed, once
|
||||
return
|
||||
for n in get_tree().get_nodes_in_group("smashable"):
|
||||
var b := n as RigidBody3D
|
||||
if b == null or b.freeze:
|
||||
@ -637,6 +669,23 @@ func _physics_process(dt: float) -> void:
|
||||
if b.angular_velocity.length() > SPAWN_MAX_SPIN:
|
||||
b.angular_velocity = b.angular_velocity.normalized() * SPAWN_MAX_SPIN
|
||||
|
||||
## One pass, the frame the spawn-guard window closes: anything still DRIFTING —
|
||||
## slower than you could ever have pushed it — is put to bed. 310 packed bodies
|
||||
## (bottles touching bottles, fruit nested on fruit) trade micro-jitter through their
|
||||
## contacts for many seconds otherwise, and a site should START at rest: the gate is
|
||||
## probe_levels reading 0.00 m/s. A genuinely mis-spawned launcher is NOT masked —
|
||||
## anything over the drift threshold is left alone for the guard warning to name.
|
||||
const SETTLE_MAX_DRIFT := 0.5
|
||||
func _settle_sweep() -> void:
|
||||
for n in get_tree().get_nodes_in_group("smashable"):
|
||||
var b := n as RigidBody3D
|
||||
if b == null or b.freeze:
|
||||
continue
|
||||
if b.linear_velocity.length() < SETTLE_MAX_DRIFT and b.angular_velocity.length() < 2.0:
|
||||
b.linear_velocity = Vector3.ZERO
|
||||
b.angular_velocity = Vector3.ZERO
|
||||
b.sleeping = true
|
||||
|
||||
func _on_smashed(kind: String, at: Vector3, shards: int) -> void:
|
||||
_smash_count += 1
|
||||
_tally[kind] = int(_tally.get(kind, 0)) + 1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user