extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_definitions_intact.gd # # A guard against a failure mode that has now cost this project twice, and # both times silently. # # Opening the project in the Godot editor re-saves .tres files, and its # writer DROPS properties it considers default-valued. An empty-looking # PackedStringArray is the usual casualty: GasBurnerBuildingDefinition lost # `capabilities` and `collected_outputs` once (which took the gas burner's # whole conversion with it), and InterceptorVesselDefinition later lost # `capabilities`, `hull_points`, `build_time`, `icon` and `glow_color` - # which cost the starting vessel its cargo hold, so nothing could be mined. # # Neither showed up as an error. The resource still loaded; it was just # quietly less than it had been, and the failure surfaced somewhere else # entirely - a full suite reporting ten unrelated tests broken. # # So this asserts the SHAPE of the catalog rather than any one field: every # definition must carry the things that make it what it is, and a capability # a definition claims must have a component behind it. Anything the editor # eats fails here first, next to the thing that was eaten. # # Covers: # A. every vessel is whole - name, cost, icon, hull, and stats # B. a vessel with cargo declares it and has a capacity to go with it # C. every building is whole, and its menu wiring is complete # D. a converter building keeps the fields its conversion needs # E. no definition has lost its component list var failures: Array = [] func _fail(message: String) -> String: failures.append(message) return message # get() answers null for a property a definition's script never declared, # and null has no is_empty(). These keep a missing field from looking like # an empty one - and from taking the rest of the sweep down with it. func _text(definition: Resource, field: String) -> String: var value: Variant = definition.get(field) return "" if value == null else str(value) func _list(definition: Resource, field: String) -> Array: var value: Variant = definition.get(field) if value == null: return [] return Array(value.keys() if value is Dictionary else value) func _check_vessels() -> void: for definition in VesselDefinitions.all(): var id: String = String(definition.definition_id) # --- A. whole --------------------------------------------------- if String(definition.display_name).strip_edges() == "": _fail("vessel %s has no display name" % id) if definition.base_stats.is_empty(): _fail("vessel %s has no stats at all" % id) # Only the hulls that actually join the fleet: a repair drone is a # logistics unit driven by GameState, with nothing bolted to it. if definition.fleet_eligible and definition.component_scenes.is_empty(): _fail("vessel %s flies in the fleet with no components - an empty hull" % id) if definition.hull_points.is_empty(): _fail("vessel %s has no hull points - it would draw as the fallback triangle" % id) # --- B. cargo -------------------------------------------------- var carries: bool = float(definition.base_stats.get("cargo_capacity", 0.0)) > 0.0 var claims: bool = "cargo" in definition.capabilities if carries != claims: _fail("vessel %s: cargo_capacity %s but capabilities %s - a hold nothing can fill, or the reverse" \ % [id, definition.base_stats.get("cargo_capacity", 0.0), definition.capabilities]) if "weapon" in definition.capabilities \ and float(definition.base_stats.get("fire_range", 0.0)) <= 0.0: _fail("vessel %s claims a weapon and has no range" % id) func _check_buildings() -> void: for definition in BuildingDefinitions.all(): var id: String = String(definition.definition_id) # --- C. whole -------------------------------------------------- if String(definition.display_name).strip_edges() == "": _fail("building %s has no display name" % id) if String(definition.description).strip_edges() == "": _fail("building %s has no description - its info popup would be blank" % id) if int(definition.cost) <= 0: _fail("building %s is free" % id) if int(definition.max_hp) <= 0: _fail("building %s has no hit points" % id) # Space-placed structures only: an extractor is a marker on a planet # rather than a thing standing in the world, and has no scene. if definition.placement_mode == "space" and definition.visual_scene == null: _fail("building %s stands in space with nothing to draw" % id) for field in ["menu_button_name", "menu_label_name", "menu_cost_name"]: if str(definition.get(field)).strip_edges() == "": _fail("building %s has no %s - the build menu could not place it" % [id, field]) if definition.capabilities.is_empty(): _fail("building %s declares no capabilities - it would do nothing at all" % id) # --- D. converters --------------------------------------------- # These live on the LOGISTICS profile, a sub-resource of the # definition, not on the definition itself. Asking the definition # for them answers null and the whole check quietly never fires - # which is how the first draft of this test passed while the gas # burner sat there with its collected_outputs missing. var logistics: Resource = definition.logistics if logistics != null and _text(logistics, "conversion_input") != "": if _text(logistics, "conversion_output") == "": _fail("building %s converts something into nothing" % id) if _list(logistics, "collected_outputs").is_empty(): _fail("building %s converts but lists no collected outputs - what it makes would never be fetched" % id) if _list(logistics, "input_buffers").is_empty(): _fail("building %s converts with no input buffer" % id) func _initialize() -> void: _check_vessels() _check_buildings() if not failures.is_empty(): for message in failures: print(" - %s" % message) print("TEST_DEFINITIONS_INTACT: FAIL (%d definition(s) came back incomplete)" % failures.size()) quit(1) return print("%d vessels and %d buildings, all intact" \ % [VesselDefinitions.all().size(), BuildingDefinitions.all().size()]) print("TEST_DEFINITIONS_INTACT: PASS") quit(0)