extends SceneTree
## Gate: a map with objects but no DCLevel still folds its object records.
##
## THE BUG THIS EXISTS FOR. refold() opened with
##
## if level == null or ... or not level.has_method("apply_journal"):
## return {}
##
## and returned before touching `objects`. That conflates "this map has no brush
## level" with "there is nothing to fold". It never showed because every map with
## an object system also had a DCLevel — map_dc_level is the only map that
## instantiates SceneObjects at all (map_dc_level.gd:1169).
##
## It shows the moment the object system is brought to map_flat, which is a bare
## ground plane with no DCLevel: every object.create record would be journalled,
## replicated, and then silently never built, on every peer.
##
## Brushes still need a level and still skip without one. The two halves are
## already partitioned inside refold for a stated reason — handing object records
## to DCLevel makes it report each as a missing target — so the fix is to move the
## guard onto the brush half rather than to keep it in front of both.
var ok := true
var _ran := false
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _process(_delta: float) -> bool:
if _ran:
return true
_ran = true
_run()
print("GATE objects_without_level: %s" % ("PASS" if ok else "FAIL"))
if not ok:
quit(1)
return true
func _run() -> void:
# ⛔ load() AT RUNTIME, NOT class_name. Naming LevelPersistence / SceneObjects /
# PrefabCatalogue directly resolves them at COMPILE time, which happens before
# the autoload registry exists -- level_persistence.gd references GameState, so
# the whole gate failed with "Identifier not found: GameState" and, because the
# abort happened before any _p(), printed PASS. Same trap the repo already
# records for --check-only.
var SceneObjectsScript: GDScript = load("res://scenes/game/scene_objects.gd")
var PersistScript: GDScript = load("res://scenes/maps/level_persistence.gd")
var Catalogue: GDScript = load("res://scenes/game/prefab_catalogue.gd")
_p(SceneObjectsScript != null and PersistScript != null and Catalogue != null,
"scene_objects.gd, level_persistence.gd and prefab_catalogue.gd all load")
if SceneObjectsScript == null or PersistScript == null or Catalogue == null:
return
var objects: Node = SceneObjectsScript.new()
objects.name = "SceneObjects"
root.add_child(objects)
var persist: Node = PersistScript.new()
persist.name = "LevelPersistenceNoLevel"
persist.level = null # THE CASE: a map with no DCLevel
persist.objects = objects
root.add_child(persist)
_p(persist.level == null, "the fixture really has no level")
_p(persist.objects == objects, "and it does have an objects manager")
# A prefab id that exists, so a skip here means the FOLD did not run rather
# than that the catalogue refused it.
var type_id := "marker"
_p(Catalogue.has(type_id), "the '%s' prefab exists to create" % type_id)
var dot: String = persist.reserve_dot()
_p(dot != "", "the journal reserved a dot without a level (%s)" % dot)
if dot == "":
return
var xf := Transform3D(Basis(), Vector3(4.0, 0.0, -2.0))
var wrote: bool = persist.record_at(dot, "object.create",
{"type": type_id, "xform": SceneObjectsScript.xform_to(xf)})
_p(wrote, "and recorded an object.create")
if not wrote:
return
var stats: Dictionary = persist.refold()
_p(not stats.is_empty(),
"refold returns a result with no level -- it used to return {} and do nothing")
var os: Dictionary = stats.get("objects", {}) as Dictionary
_p(int(os.get("applied", 0)) == 1,
"the object record was APPLIED (applied=%d)" % int(os.get("applied", 0)))
_p(objects.count() == 1, "and a node exists for it (count=%d)" % objects.count())
var n: Node = objects.node_for(dot)
_p(n != null, "the node resolves from its dot")
if n is Node3D:
_p((n as Node3D).transform.origin.is_equal_approx(xf.origin),
"and landed where the record said (%s)" % str((n as Node3D).transform.origin))
# SELF-CHECK: the fold must be doing the work, not the recording. A second
# record that is NOT folded must not produce a node.
var dot2: String = persist.reserve_dot()
persist.record_at(dot2, "object.create",
{"type": type_id, "xform": SceneObjectsScript.xform_to(Transform3D())})
_p(objects.count() == 1,
"self-check: recording alone builds nothing (still %d)" % objects.count())
persist.refold()
_p(objects.count() == 2,
"self-check: and the fold is what builds it (now %d)" % objects.count())
persist.free()
objects.free()