diff --git a/game/dev/probe_levels.gd b/game/dev/probe_levels.gd index 511e50e..b1c9bb6 100644 --- a/game/dev/probe_levels.gd +++ b/game/dev/probe_levels.gd @@ -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" % [ diff --git a/game/scripts/Main.gd b/game/scripts/Main.gd index a7c76ff..ef19d91 100644 --- a/game/scripts/Main.gd +++ b/game/scripts/Main.gd @@ -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