Export venues as editor-authorable .tres resources
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Successful in 3m51s

- tools/export_venues.gd (SceneTree script) saves the three factory scenarios to
  content/venues/<id>.tres via ResourceSaver. The factory stays the source of truth;
  the exported files are what an editor workflow would edit and ship.
- New tests load the files back and check that id, name, event pool, profiles,
  policy (ID/wand), equipment, door-staff lines, shift phases, and locations all
  survive. They also fail when the exported files are missing or stale, so the
  export is re-run after content changes.
- GUT is consciously not adopted: the custom headless runner already covers 157
  assertions without an editor plugin and keeps CI dependency-free. Recorded in
  DONE.md.
- TODO: the planned backlog is complete; only optional future ideas remain.
- README/MEMORY updated. Version 0.2.16.
This commit is contained in:
zwitschi committed 2026-10-31 22:16:55 +01:00
1 parent 72b9378e69
commit f763cff744
10 files changed
+2318 -9

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
extends RefCounted
## Venues can be authored as `.tres` resources: the built-in factories export and
## round-trip without losing their data (optional editor workflow, TODO tooling).
const Factory := preload("res://src/content/scenario_factory.gd")
const Rules := preload("res://src/core/rules.gd")
const VenueScenario := preload("res://src/core/venue_scenario.gd")
func test_venues_round_trip_through_tres() -> bool:
var scenarios := [Factory.hollywood_building(), Factory.joke_shop(), Factory.tv_studio()]
for scenario in scenarios:
var path := "user://%s_roundtrip.tres" % scenario.id
var err: int = ResourceSaver.save(scenario, path)
if err != OK:
return TestUtils.fail("could not save %s (error %d)" % [scenario.id, err])
var loaded = ResourceLoader.load(path)
if not (loaded is VenueScenario):
return TestUtils.fail("%s did not load back as VenueScenario" % scenario.id)
var a := TestUtils.check_eq(loaded.id, scenario.id, "%s id survives" % scenario.id)
var b := TestUtils.check_eq(loaded.display_name, scenario.display_name, "%s name survives" % scenario.id)
var c := TestUtils.check_eq(loaded.event_pool.size(), scenario.event_pool.size(), "%s event pool survives" % scenario.id)
var d := TestUtils.check_eq(loaded.npc_profiles.size(), scenario.npc_profiles.size(), "%s profiles survive" % scenario.id)
var e := TestUtils.check_eq(loaded.waypoint_for_intent("sit"), scenario.waypoint_for_intent("sit"), "%s locations survive" % scenario.id)
if not (a and b and c and d and e):
return false
return true
func test_exported_tres_files_match_the_factory() -> bool:
## content/venues/*.tres is generated by tools/export_venues.gd; make sure it is
## present and not stale relative to the factory.
for scenario in [Factory.hollywood_building(), Factory.joke_shop(), Factory.tv_studio()]:
var path := "res://content/venues/%s.tres" % scenario.id
if not FileAccess.file_exists(path):
return TestUtils.fail("missing exported venue %s — run tools/export_venues.gd" % path)
var loaded = ResourceLoader.load(path)
if not (loaded is VenueScenario):
return TestUtils.fail("%s is not a VenueScenario" % path)
if loaded.event_pool.size() != scenario.event_pool.size():
return TestUtils.fail("%s is stale (events %d vs %d) — re-run tools/export_venues.gd" % [
path, loaded.event_pool.size(), scenario.event_pool.size()])
if not TestUtils.check_eq(loaded.shift_duration_s, scenario.shift_duration_s, "%s duration matches" % scenario.id):
return false
return true
func test_exported_policy_and_events_intact() -> bool:
var loaded = ResourceLoader.load("res://content/venues/joke_shop.tres")
var a := TestUtils.check(loaded != null and loaded.access_policy != null, "access policy survives export")
var b := TestUtils.check_eq(loaded.access_policy.require_id, true, "joke shop still requires ID")
var c := TestUtils.check_eq(loaded.access_policy.wand_screening_required, true, "wand screening still required")
var d := TestUtils.check_eq(loaded.equipment.has("wand"), true, "equipment survives export")
var ids: Array = []
for event in loaded.event_pool:
ids.append(str((event as Dictionary).get("id", "")))
var e := TestUtils.check(ids.has("joke_refuses_wand"), "authored events survive export")
var f := TestUtils.check(loaded.door_staff_lines.has("refusal"), "door-staff lines survive export")
return a and b and c and d and e and f
func test_exported_phases_survive() -> bool:
var loaded = ResourceLoader.load("res://content/venues/hollywood_building.tres")
var a := TestUtils.check(loaded.shift_phases.has(Rules.Phase.LATE), "late phase survives export")
var b := TestUtils.check_eq(int(loaded.shift_phases[Rules.Phase.LATE].get("start_s", -1)), 700, "late start preserved")
return a and b