The hip-hop room has stock (7 racks, 47 bins, 2,865 records); the entry hall and back room are empty. Import all four anyway: the empty rooms are the connective tissue, and they are the only floor in the building big enough to get an office chair up to speed. A portal pair is one doorway seen from both sides, so the importer walks the graph and places each room by making its portal land exactly on its neighbour's. Every pair here sits on parallel walls with opposite facings, so each join is a pure translation — and when one isn't, it says so rather than quietly getting it wrong. It then recentres the union, emits the interior walls with a gap at each doorway, and puts the spawn in the entry hall, because that is the way in. hiphop-room <-> entry-hall <-> shop-floor <-> back-room 12.96 x 15.41 m - 36 racks - 325 bins - 302 real genre labels - 24,024 records Levels.record_store() now takes the walls and rooms from the import, hangs the street glazing on the entry hall's outer wall, and lays a light grid across whatever footprint the rooms came out as. probe_store.gd also checks the new facts: five doorways exist, and the spawn has nothing in it (excluding the player, who is supposed to be standing there — that false positive was the probe's bug, not the level's). All gates green: smoke clean, 7/7 sites 0.00 m/s, overlap CLEAN, all probes pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
120 lines
3.6 KiB
GDScript
120 lines
3.6 KiB
GDScript
extends SceneTree
|
|
|
|
## SITE 7 contract — the imported shop:
|
|
## - it builds, with racks AND bins
|
|
## - the cascade came off the shop's own data: racks hold bins via `supported_by`
|
|
## - smashing one rack releases exactly ITS bins, and no others
|
|
## - the real genre labels survived the import
|
|
##
|
|
## Godot --headless --path game --script dev/probe_store.gd
|
|
|
|
func _initialize() -> void:
|
|
var main: Node = (load("res://main.tscn") as PackedScene).instantiate()
|
|
get_root().add_child(main)
|
|
await process_frame
|
|
main._load_level("store")
|
|
for i in 70:
|
|
await physics_frame
|
|
|
|
var racks: Array = []
|
|
var bins: Array = []
|
|
var labelled := 0
|
|
for n in get_nodes_in_group("smashable"):
|
|
var s := n as Smashable
|
|
if s == null:
|
|
continue
|
|
if s.supports.size() > 0:
|
|
racks.append(s)
|
|
if s.freeze:
|
|
bins.append(s)
|
|
if s.has_meta("label"):
|
|
labelled += 1
|
|
print("racks holding bins: %d frozen bins: %d labelled bins: %d" % [
|
|
racks.size(), bins.size(), labelled])
|
|
|
|
# --- the four rooms, and that you can actually walk between them -------------------
|
|
var spec: Dictionary = Levels.record_store()
|
|
var walls: Array = spec.get("walls", [])
|
|
var doors := 0
|
|
for w in walls:
|
|
if (w as Dictionary).has("door"):
|
|
doors += 1
|
|
print("interior walls: %d, of which %d have a doorway" % [walls.size(), doors])
|
|
if doors < 3:
|
|
print("FAIL: the rooms are walled off from each other (need a door per portal)")
|
|
quit(1)
|
|
return
|
|
# spawn must be standing in open air, not inside a wall or a rack
|
|
var sp: Vector3 = spec["spawn"]["at"]
|
|
var space := main.get_viewport().world_3d.direct_space_state
|
|
var q := PhysicsShapeQueryParameters3D.new()
|
|
var cap := CapsuleShape3D.new()
|
|
cap.radius = 0.34
|
|
cap.height = 1.7
|
|
q.shape = cap
|
|
q.transform = Transform3D(Basis.IDENTITY, sp + Vector3(0, 0.9, 0))
|
|
# the player is already standing here — that's the point, not an obstruction
|
|
var pl = main.get("_player")
|
|
if pl != null:
|
|
q.exclude = [(pl as CollisionObject3D).get_rid()]
|
|
var blocked := space.intersect_shape(q, 4)
|
|
var names := ""
|
|
for b in blocked:
|
|
var c = b.get("collider")
|
|
if c is Node:
|
|
names += " " + String((c as Node).name)
|
|
print("spawn at %s — %d bodies in the way:%s" % [sp.snappedf(0.01), blocked.size(), names])
|
|
if blocked.size() > 0:
|
|
print("FAIL: spawn is inside geometry")
|
|
quit(1)
|
|
return
|
|
if racks.is_empty() or bins.is_empty():
|
|
print("FAIL: the shop did not import (no racks holding bins)")
|
|
quit(1)
|
|
return
|
|
if labelled < 20:
|
|
print("FAIL: the real genre labels did not survive the import")
|
|
quit(1)
|
|
return
|
|
|
|
# find the rack carrying the most bins and knock it over
|
|
var best: Smashable = racks[0]
|
|
for r in racks:
|
|
if r.supports.size() > best.supports.size():
|
|
best = r
|
|
var mine: Array = []
|
|
for b in best.supports:
|
|
if is_instance_valid(b):
|
|
mine.append(b)
|
|
var others: Array = []
|
|
for b in bins:
|
|
if is_instance_valid(b) and not mine.has(b):
|
|
others.append(b)
|
|
var others_frozen_before := 0
|
|
for b in others:
|
|
if (b as RigidBody3D).freeze:
|
|
others_frozen_before += 1
|
|
|
|
print("smashing '%s' — it carries %d bins" % [best.name, mine.size()])
|
|
best.smash(Vector3(0, 1, 0), 999.0)
|
|
await physics_frame
|
|
await physics_frame
|
|
|
|
var released := 0
|
|
for b in mine:
|
|
if is_instance_valid(b) and not (b as RigidBody3D).freeze:
|
|
released += 1
|
|
var others_still := 0
|
|
for b in others:
|
|
if is_instance_valid(b) and (b as RigidBody3D).freeze:
|
|
others_still += 1
|
|
print("its bins released: %d/%d other bins still frozen: %d/%d" % [
|
|
released, mine.size(), others_still, others_frozen_before])
|
|
|
|
if released == mine.size() and others_still == others_frozen_before:
|
|
print("STORE OK")
|
|
quit(0)
|
|
else:
|
|
print("FAIL: cascade released the wrong set of bins")
|
|
quit(1)
|