Files
Papay-Shooter/debug/sakura_followup_audit.gd
T
2026-08-02 02:20:02 -04:00

232 lines
8.5 KiB
GDScript

extends SceneTree
## Requirement-level audit for the park/shrine/intersection follow-up pass.
const MAP := "res://scenes/maps/sakura_crossing/sakura_crossing.tscn"
const ROAD_HALF := 4.5
const PAVE_W := 2.6
const SHOP_ST_Z := -30.0
const SOUTH_ST_Z := 20.0
const EAST_ST_X := 64.0
const EAST_ST_Z := 54.0
const STREET_HALF := 4.5
var _level: Node
var _frames := 0
var _fails := 0
func _initialize() -> void:
_level = (load(MAP) as PackedScene).instantiate()
root.add_child(_level)
func _ok(condition: bool, message: String) -> void:
if condition:
print("FOLLOWUP_AUDIT OK ", message)
else:
print("FOLLOWUP_AUDIT FAIL ", message)
_fails += 1
func _all_nodes() -> Array[Node]:
var result: Array[Node] = []
var queue: Array[Node] = [_level]
while not queue.is_empty():
var node: Node = queue.pop_front()
result.append(node)
for child in node.get_children():
queue.append(child)
return result
func _base_name(node: Node) -> String:
var value := String(node.name)
while value.length() > 0 and value[value.length() - 1].is_valid_int():
value = value.substr(0, value.length() - 1)
return value
func _named(name: String) -> Array[Node]:
var result: Array[Node] = []
for node in _all_nodes():
if _base_name(node) == name:
result.append(node)
return result
func _box_size(node: Node) -> Vector3:
var collisions := node.find_children("*", "CollisionShape3D", true, false)
if collisions.is_empty():
return Vector3.ZERO
var collision := collisions[0] as CollisionShape3D
if collision == null or not collision.shape is BoxShape3D:
return Vector3.ZERO
return (collision.shape as BoxShape3D).size
func _on_road_or_pavement(p: Vector2) -> bool:
var paved_half := ROAD_HALF + PAVE_W + 0.30
if absf(p.x) <= paved_half:
return true
if absf(p.y - SOUTH_ST_Z) <= paved_half:
return true
if p.x <= 52.0 and absf(p.y - SHOP_ST_Z) <= paved_half:
return true
if p.x >= ROAD_HALF - 0.2 and p.x <= 108.0 \
and absf(p.y - EAST_ST_Z) <= paved_half:
return true
if p.y >= SOUTH_ST_Z and p.y <= 80.0 \
and absf(p.x - EAST_ST_X) <= paved_half:
return true
return false
func _audit_signs(nodes: Array[Node]) -> void:
var signs := 0
for node in nodes:
if not node.has_meta("ground_sign"):
continue
signs += 1
var g := (node as Node3D).global_position
_ok(not _on_road_or_pavement(Vector2(g.x, g.z)),
"ground sign %s is clear of roads, crosswalks and pavements" % node.name)
_ok(signs == 6, "all six freestanding sign posts are tagged and checked")
func _audit_sidewalk_connectors(nodes: Array[Node]) -> void:
var connectors := 0
var full_size := true
for node in nodes:
if not node.has_meta("sidewalk_connector"):
continue
connectors += 1
var collisions := node.find_children("*", "CollisionShape3D", true, false)
var collision: CollisionShape3D = collisions[0] if not collisions.is_empty() else null
if collision == null or not collision.shape is BoxShape3D:
full_size = false
continue
var size := (collision.shape as BoxShape3D).size
if size.x < PAVE_W + 0.59 or size.z < PAVE_W + 0.59:
full_size = false
_ok(connectors == 10, "eight main-junction and two shrine-side corner connectors exist")
_ok(full_size, "every tactile apron reaches the adjoining pavement on both axes")
func _audit_east_link(nodes: Array[Node]) -> void:
var links: Array[Node] = []
for node in nodes:
if node.has_meta("east_street_link"):
links.append(node)
_ok(links.size() == 1, "the shrine-side neighbourhood road has one continuous link slab")
if links.size() == 1:
var body := links[0] as StaticBody3D
var collisions := body.find_children("*", "CollisionShape3D", true, false)
var collision: CollisionShape3D = collisions[0] if not collisions.is_empty() else null
var size := (collision.shape as BoxShape3D).size
var left_edge := body.global_position.x - size.x * 0.5
_ok(left_edge <= ROAD_HALF, "the neighbourhood branch physically reaches the main road")
var query := PhysicsRayQueryParameters3D.create(
Vector3(6.0, 2.0, EAST_ST_Z), Vector3(6.0, -1.0, EAST_ST_Z))
var hit: Dictionary = _level.get_world_3d().direct_space_state.intersect_ray(query)
var collider: Node = hit.get("collider") as Node
_ok(not hit.is_empty() and collider != null and _base_name(collider) == "EastStreetLink",
"the former gap is road surface, not the old sidewalk barrier")
func _audit_park_and_playground() -> void:
_ok(_named("PondBridge").size() == 1, "north park has a connected pond-loop bridge")
_ok(_named("PicnicTable").size() >= 3, "park system has multiple social/picnic nodes")
_ok(_named("ParkLamp").size() >= 8, "park paths have a consistent lighting rhythm")
_ok(_named("ParkBin").size() >= 4, "park entrances and activity nodes have service assets")
_ok(_named("SwingBeam").size() == 1 and _named("SwingSeat").size() == 2 \
and _named("SwingChain").size() == 4,
"two swing seats align to four chains and one overhead beam")
var beams := _named("SwingBeam")
var seats := _named("SwingSeat")
var chains := _named("SwingChain")
if beams.size() == 1 and seats.size() == 2 and chains.size() == 4:
var beam := beams[0] as Node3D
var beam_size := _box_size(beam)
var beam_left := beam.global_position.x - beam_size.x * 0.5
var beam_right := beam.global_position.x + beam_size.x * 0.5
var chains_under_beam := true
for chain in chains:
var cp := (chain as Node3D).global_position
chains_under_beam = chains_under_beam \
and cp.x >= beam_left and cp.x <= beam_right \
and absf(cp.z - beam.global_position.z) < 0.01 \
and absf((cp.y + 1.55 * 0.5) - (beam.global_position.y - beam_size.y * 0.5)) < 0.08
_ok(chains_under_beam, "every swing chain lands under and touches the common top beam")
var seats_have_pairs := true
for seat in seats:
var sp := (seat as Node3D).global_position
var matching := 0
for chain in chains:
var cp := (chain as Node3D).global_position
if absf(cp.x - sp.x) <= 0.55 and absf(cp.z - sp.z) < 0.01:
matching += 1
seats_have_pairs = seats_have_pairs and matching == 2
_ok(seats_have_pairs, "each swing seat is centered beneath exactly two chains")
_ok(_named("SlidePlatform").size() == 1 and _named("SlideSlope").size() == 1,
"slide platform and slope are both present")
var platforms := _named("SlidePlatform")
var slopes := _named("SlideSlope")
if platforms.size() == 1 and slopes.size() == 1:
var platform := platforms[0] as Node3D
var slope := slopes[0] as Node3D
var platform_size := _box_size(platform)
var slope_size := _box_size(slope)
var high_end := slope.global_transform * Vector3(0.0, 0.0, slope_size.z * 0.5)
var platform_edge := Vector3(platform.global_position.x,
platform.global_position.y, platform.global_position.z - platform_size.z * 0.5)
_ok(Vector2(high_end.y, high_end.z).distance_to(
Vector2(platform_edge.y, platform_edge.z)) < 0.16,
"slide incline meets the platform edge at the same height and z datum")
_ok(_named("Sandpit").size() == 1 and _named("Seesaw").size() == 1 \
and _named("ClimbingFrame").size() == 1,
"playground contains distinct sand, seesaw and climbing zones")
var cherries: Array[Node] = []
var cedars: Array[Node] = []
for node in _named("Cherry"):
var p := (node as Node3D).global_position
if p.x >= -96.0 and p.x <= 92.0 and p.z < -70.0:
cherries.append(node)
for node in _named("Cedar"):
var p := (node as Node3D).global_position
if p.x >= -96.0 and p.x <= 92.0 and p.z <= -78.0 and p.z >= -89.0:
cedars.append(node)
var min_gap := INF
for cherry in cherries:
var cp := (cherry as Node3D).global_position
for cedar in cedars:
var ep := (cedar as Node3D).global_position
min_gap = minf(min_gap, Vector2(cp.x, cp.z).distance_to(Vector2(ep.x, ep.z)))
_ok(not cherries.is_empty() and not cedars.is_empty() and min_gap > 8.0,
"north-park cherry and evergreen planting bands are spatially separated")
func _audit_shrine() -> void:
_ok(_named("Komainu").size() >= 4, "outer and inner shrine thresholds have guardian pairs")
_ok(_named("ShrineBanner").size() >= 4, "shrine approach and precinct have banner rhythm")
_ok(_named("OmikujiRack").size() == 1, "precinct has a dedicated fortune-tying rack")
_ok(_named("SacredTree").size() == 1 and _named("Hokora").size() == 1,
"shrine lawn has a sacred tree and auxiliary shrine destination")
func _process(_delta: float) -> bool:
_frames += 1
if _frames < 120:
return false
var nodes := _all_nodes()
_audit_signs(nodes)
_audit_sidewalk_connectors(nodes)
_audit_east_link(nodes)
_audit_park_and_playground()
_audit_shrine()
print("=== SAKURA FOLLOW-UP AUDIT: %s ===" %
("ALL CLEAR" if _fails == 0 else "%d FAILED" % _fails))
quit(0 if _fails == 0 else 1)
return true