The level the whole game started as, and the only one that isn't authored. robotmonster.party/store/ is assembled procedurally from Postgres (the wowplatter virtual_* schema, served by recordgod), and this game's sites are already data — so tools/import_store.py reads the live shop and emits assets/store/shop_floor.json: the actual 6.66 x 9.51 m floor, 35 racks across 21 archetypes, 278 bins carrying their real genre labels (HARD TRANCE, $12-$15 EURO/ITALO HOUSE), 21,159 records of stock. Levels.record_store() loads it; 'store' joins ORDER as the seventh site. The shop's schema IS the cascade. virtual_crate.rack_id already records which rack holds which bin, so bins arrive frozen and supported_by their rack and none of the structure is authored. To carry that, smashables gained two optional generic keys — 'id' and 'supported_by' — so any level can express support as data. Every existing level is unaffected. Two reconciliations, because a storefront is a renderer: - Mesh sizes are a lie: 21 archetypes share a few stand-in GLBs, so each piece carries 'fit' (the archetype's real dims from the shop's table) and _glb_piece scales the visual to it and builds the collider from it. Also fixed racks being flattened to the floor — pos_y is a piece's BOTTOM, which is what sit_on means, so the ceiling beams, aircon and wall shelves now stay up where they belong. - Nothing collides in a renderer: racks that sat inside each other for years get separated (weighted, so racks barely move and aisles keep their shape — 3 m of correction across 35), rack footprints inset 1.5 cm a side for the few genuinely wedged units, and the importer AUDITS ITS OWN OUTPUT with the same test probe_overlap runs. Bins are deliberately not separated from their rack: cargo inside a container is what the shop means, they spawn frozen, and the audit skips frozen bodies because a frozen body cannot be depenetrated across the room. dev/probe_store.gd: 22 racks hold bins, 255 labels survived, and smashing the rack carrying 22 bins releases exactly those 22 while the other 256 stay frozen. All gates green: smoke clean, 7/7 sites 0.00 m/s, overlap CLEAN, LANE9 probes pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.3 KiB
GDScript
83 lines
2.3 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])
|
|
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)
|