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>
53 lines
1.7 KiB
GDScript
53 lines
1.7 KiB
GDScript
extends SceneTree
|
|
|
|
## Build every level in Levels.ORDER and report whether it's structurally sound:
|
|
## prop count, missing assets, and — the one that matters — whether anything is spawned
|
|
## inside anything else. A level that fires furniture across the room on frame one is
|
|
## broken whatever it looks like in a screenshot.
|
|
##
|
|
## Godot --headless --path game --script dev/probe_levels.gd
|
|
|
|
func _initialize() -> void:
|
|
var main: Node = (load("res://main.tscn") as PackedScene).instantiate()
|
|
get_root().add_child(main)
|
|
await process_frame
|
|
|
|
for id in Levels.ORDER:
|
|
main._load_level(id)
|
|
for i in 8:
|
|
await physics_frame
|
|
var plan = main.get("_plan")
|
|
var bodies := get_nodes_in_group("smashable").size()
|
|
var statics := _count_static(plan)
|
|
# let it run a full second and see if anything is still moving
|
|
for i in 60:
|
|
await physics_frame
|
|
var motion := 0.0
|
|
var worst := ""
|
|
var worst_v := 0.0
|
|
for n in get_nodes_in_group("smashable"):
|
|
var b := n as RigidBody3D
|
|
if b == null or b.freeze:
|
|
continue
|
|
var v := b.linear_velocity.length()
|
|
motion += v
|
|
if v > worst_v:
|
|
worst_v = v
|
|
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" % [
|
|
motion, worst if worst != "" else "-"])
|
|
quit()
|
|
|
|
func _count_static(n: Node) -> int:
|
|
var c := 0
|
|
if n is StaticBody3D:
|
|
c += 1
|
|
for ch in n.get_children():
|
|
c += _count_static(ch)
|
|
return c
|