Initial 100, capacity 300, world tree +20 per 10 game minutes. Each boundary pays registered upkeep demand before storing surplus; overflow is discarded and recorded, 0 stops the whole tower. Furnace offers are judged after generation and demand payment. New saves carry mana_reserve and Operations rollback includes it; legacy saves keep a 0..300 amount and reject others. Outage tests that relied on Mana 0 persisting now hold the outage with a disabled demand sink, since the world tree recovers at the next boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
204 lines
12 KiB
GDScript
204 lines
12 KiB
GDScript
extends SceneTree
|
|
const ManaOutage = preload("res://tests/helpers/mana_outage.gd")
|
|
|
|
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
|
|
var _finishing := false
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _state: Variant
|
|
var _serial := 0
|
|
var _main: Control
|
|
var _saw_trip := false
|
|
var _saw_boarded := false
|
|
var _saw_arrival := false
|
|
var _saved := false
|
|
var _preview_snapshot: Dictionary = {}
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
func _run() -> void:
|
|
_runtime = root.get_node("SimulationRuntime")
|
|
_clock = root.get_node("GameClock")
|
|
_state = root.get_node("GameState")
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
_runtime.initialize(_clock, _state, 424242)
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
_clock.set_paused(true)
|
|
root.size = Vector2i(1440, 810)
|
|
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
|
_main.set("manual_save_path", "user://tests/transport_%d/manual" % OS.get_process_id())
|
|
root.add_child(_main)
|
|
await process_frame
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
_expect(_state.get_resource(&"gold") == 5000 and _state.get_resource(&"materials") == 50, "ordinary starting budget")
|
|
if not _build(&"guest_elevator", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)):
|
|
_finish(); return
|
|
_clock.advance_minutes(300)
|
|
_expect(_runtime.elevators.get_report()[&"shafts"].size() == 1, "completed construction registers physical cabin")
|
|
if _runtime.elevators.get_report()[&"shafts"].is_empty(): _finish(); return
|
|
if not _build(&"guest_elevator", Vector3i(0, 2, 0)) or not _build(&"standard_room", Vector3i(2, 2, 0)):
|
|
_finish(); return
|
|
_clock.advance_minutes(300)
|
|
if not _build(&"standard_room", Vector3i(2, 3, 0)):
|
|
_finish(); return
|
|
_clock.advance_minutes(60)
|
|
var report: Dictionary = _runtime.elevators.get_report()
|
|
_expect(report[&"shafts"].size() == 1 and report[&"shafts"][0][&"stops"].size() == 4, "stacked modules share one cabin across four floors")
|
|
_expect(_state.get_resource(&"materials") == 2, "buildings fit within 50 materials")
|
|
var reception: FacilityInstance
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"reception": reception = facility
|
|
_expect(_cmd(&"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire receptionist with earned starting funds")
|
|
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": reception.instance_id, &"template_id": &"DAY"}).is_success(), "assign real commute")
|
|
_expect(reception.active_staff_ids.is_empty(), "assignment alone does not open reception")
|
|
for index: int in range(250):
|
|
_clock.advance_minutes(1)
|
|
var current: Dictionary = _runtime.elevators.get_report()
|
|
if not current[&"trips"].is_empty(): _saw_trip = true
|
|
for trip: Dictionary in current[&"trips"]:
|
|
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
|
_saw_boarded = true
|
|
if not _saved:
|
|
_saved = _verify_mid_ride()
|
|
_preview_snapshot = _runtime.create_snapshot()
|
|
for guest: GuestRuntime in _runtime.guest_service.get_guests():
|
|
if guest.current_cell.y >= 2: _saw_arrival = true
|
|
if _saw_boarded and not _main.get_meta(&"captured", false) and OS.get_environment("THE_TREE_TRANSPORT_CAPTURE") == "1":
|
|
_main.set_meta(&"captured", true)
|
|
await _capture()
|
|
if index % 30 == 0: await process_frame
|
|
if not _saw_trip:
|
|
print("TRANSPORT_DIAGNOSTIC ", _runtime.elevators.get_report(), " STAFF ", _runtime.staff_commute.get_report())
|
|
for f: FacilityInstance in _runtime.facility_service.get_instances(): print("FAC ", f.definition_id, " ", f.state, " ", f.active_staff_ids)
|
|
for g: GuestRuntime in _runtime.guest_service.get_guests(): print("GUEST ", g.guest_id, " ", g.state, " ", g.current_cell, " ", _runtime._project_guest_candidates(g.guest_id))
|
|
_expect(_saw_trip, "natural guest requests an elevator")
|
|
_expect(_saw_boarded, "natural guest boards the shared cabin")
|
|
_expect(_saw_arrival, "natural guest physically reaches an upper floor")
|
|
_expect(_saved, "mid-ride replay tested")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "current completed runtime restores")
|
|
if OS.get_environment("THE_TREE_TRANSPORT_PREVIEW") == "1" and _failures == 0:
|
|
_expect(_runtime.restore_snapshot(_preview_snapshot), "preview returns to the verified first boarding")
|
|
if _failures > 0: _finish(); return
|
|
await _capture()
|
|
DisplayServer.window_set_title("The Tree · 昇降機と通勤の試遊(保存なし)")
|
|
_clock.set_paused(true)
|
|
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
|
print("TRANSPORT_PREVIEW_READY Space resumes; test saves disabled")
|
|
return
|
|
_finish()
|
|
|
|
func _verify_mid_ride() -> bool:
|
|
var save_path := "user://tests/transport_%d/ride" % OS.get_process_id()
|
|
_expect(bool(_runtime.save_to_path(save_path).get(&"ok", false)), "boarded save writes isolated disk slot")
|
|
_expect(bool(_runtime.load_from_path(save_path).get(&"ok", false)), "boarded disk slot reloads")
|
|
var snapshot: Dictionary = _runtime.create_snapshot()
|
|
_expect(snapshot[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "runtime schema14 records elevator state and explicit vertical routing")
|
|
_expect(_runtime.restore_snapshot(snapshot), "mid-ride Runtime snapshot restores actor/cabin/Path ownership")
|
|
var shaft: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
|
|
var busy := _cmd(&"demolish_facility", {&"instance_id": shaft[&"module_ids"][1]})
|
|
_expect(not busy.is_success() and busy.details.get(&"reason") == &"ELEVATOR_BUSY", "occupied shaft demolition rejected before spending or topology changes")
|
|
_expect(_runtime.restore_snapshot(snapshot), "snapshot resets rejected command metadata")
|
|
# Diagnose total reserve depletion while a natural passenger is aboard.
|
|
var riders: Array[StringName] = []
|
|
for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]:
|
|
if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED:
|
|
riders.append(trip[&"actor_id"])
|
|
_expect(not riders.is_empty(), "depletion drill starts with actual boarded passenger")
|
|
_state.set_resource(&"mana", 0)
|
|
_clock.advance_minutes(1)
|
|
var outage: Dictionary = _runtime.elevators.get_report()
|
|
_expect(not outage[&"shafts"][0][&"powered"] and outage[&"trips"].is_empty(), "reserve outage stops cabin and releases trip ownership")
|
|
for actor_id: StringName in riders:
|
|
_expect(_runtime.path_service.get_active_elevator_request(actor_id).is_empty(), "outage releases Path passenger request")
|
|
var safe_arrivals := 0
|
|
for event: SimulationDomainEvent in _runtime.domain_events.recent_events():
|
|
if event.event_type == &"ELEVATOR_ARRIVED" and event.absolute_minute == _clock.absolute_minute and event.payload.get(&"actor_id", &"") == actor_id and bool(event.payload.get(&"emergency", false)):
|
|
if event.payload.get(&"cell") in shaft[&"stops"]:
|
|
safe_arrivals += 1
|
|
_expect(safe_arrivals == 1, "boarded passenger alights exactly once at a valid shaft stop")
|
|
var outage_snapshot: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.restore_snapshot(outage_snapshot), "emergency alighting and empty reserve survive Runtime save")
|
|
_state.set_resource(&"mana", 100)
|
|
_clock.advance_minutes(1)
|
|
_expect(_runtime.elevators.get_report()[&"shafts"][0][&"powered"], "cabin supply returns when reserve recovers")
|
|
_expect(_runtime.restore_snapshot(snapshot), "rewind depletion drill to original passenger state")
|
|
for cell: Vector3i in shaft[&"stops"]:
|
|
_runtime.path_service.configure_cell_traffic(cell, 4, 0, true)
|
|
ManaOutage.hold(_runtime, _state)
|
|
_clock.advance_minutes(1)
|
|
for actor_id: StringName in riders:
|
|
_expect(_runtime.elevators.get_actor_trip(actor_id).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "no safe landing retains boarded passenger instead of teleporting")
|
|
var held_shaft: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
|
|
_clock.advance_minutes(20)
|
|
var held_after: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
|
|
_expect(held_after[&"remaining_minutes"] == held_shaft[&"remaining_minutes"], "powerless held cabin does not advance physical journey")
|
|
var held_snapshot: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.restore_snapshot(held_snapshot) and _runtime.create_snapshot() == held_snapshot, "no-landing outage preserves full Runtime save and passenger ownership")
|
|
for cell: Vector3i in shaft[&"stops"]:
|
|
_runtime.path_service.configure_cell_traffic(cell)
|
|
ManaOutage.release(_runtime, _state)
|
|
_clock.advance_minutes(45)
|
|
for actor_id: StringName in riders:
|
|
_expect(_runtime.elevators.get_actor_trip(actor_id).is_empty() and _runtime.path_service.get_active_elevator_request(actor_id).is_empty(), "landing repair and power restoration release held trip ownership")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "recovered held cabin remains saveable")
|
|
_expect(_runtime.restore_snapshot(snapshot), "restore original ride after no-landing drill")
|
|
_clock.advance_minutes(45)
|
|
var expected: Dictionary = _runtime.create_snapshot()
|
|
_expect(_runtime.restore_snapshot(snapshot), "rewind boarded save for deterministic replay")
|
|
_clock.advance_minutes(45)
|
|
_expect(_runtime._strict_variants_equal(expected, _runtime.create_snapshot()), "mid-ride save replays all Runtime state identically")
|
|
_expect(_runtime.restore_snapshot(snapshot), "restore original ride position after replay test")
|
|
var stopped := _cmd(&"set_elevator_operation", {&"shaft_id": shaft[&"shaft_id"], &"operator_open": false})
|
|
_expect(stopped.is_success(), "player stops elevator through public command")
|
|
_expect(_runtime.elevators.get_report()[&"trips"].is_empty(), "stop releases waiting and disembarks passengers at safe floor")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "stopped elevator save restores")
|
|
_expect(_runtime.restore_snapshot(snapshot), "resume original operating snapshot")
|
|
return true
|
|
|
|
func _capture() -> void:
|
|
var preview: Control = _main.get("world_preview")
|
|
_main.call("_on_facility_selected", _runtime.elevators.get_report()[&"shafts"][0][&"module_ids"][0])
|
|
preview.call("set_zoom", 2.0)
|
|
preview.set("pan_offset", preview.get("pan_offset") + preview.size * Vector2(0.5, 0.55) - preview.call("cell_to_canvas", Vector3i(2, 1, 0)))
|
|
preview.queue_redraw()
|
|
await process_frame
|
|
await RenderingServer.frame_post_draw
|
|
for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]:
|
|
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
|
var key := String(trip[&"actor_id"]).replace("guest:", "guest/").replace("staff:", "staff/")
|
|
_expect((preview.get("_boarded_actor_ids") as Dictionary).has(key), "live rider identified in cabin rendering")
|
|
_expect(not (preview.get("_actor_tracks") as Dictionary).has(key), "live rider is not duplicated on floor")
|
|
var path := ProjectSettings.globalize_path("res://artifacts/transport/elevator-cabin.png")
|
|
root.get_texture().get_image().save_png(path)
|
|
|
|
func _build(id: StringName, origin: Vector3i) -> bool:
|
|
var result := _cmd(&"build_facility", {&"definition_id": id, &"origin": origin})
|
|
_expect(result.is_success(), "build %s at %s %s" % [id, origin, result.message])
|
|
return result.is_success()
|
|
|
|
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
|
_serial += 1
|
|
return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("transport.test.%d" % _serial))
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if not condition: _failures += 1
|
|
print("%s %s" % ["PASS" if condition else "FAIL", message])
|
|
|
|
func _finish() -> void:
|
|
if _finishing:
|
|
return
|
|
_finishing = true
|
|
if is_instance_valid(_main):
|
|
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
|
|
if not bool(final_cleanup.get(&"passed", false)):
|
|
_failures += 1
|
|
push_error("ordinary Main final audio cleanup failed")
|
|
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
|
quit(1 if _failures else 0)
|