162 lines
8.6 KiB
GDScript
162 lines
8.6 KiB
GDScript
extends "res://tests/fixtures/p08-03/placement.gd"
|
|
## Real Runtime commands; inherited formal codec/save-operation helpers unchanged.
|
|
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
|
const Comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd")
|
|
var stream: RefCounted
|
|
var layout := "A"
|
|
var facility_ids: Dictionary = {}
|
|
|
|
func setup() -> void:
|
|
clock.reset(1, 6, 0)
|
|
var state: Variant = tree.root.get_node("GameState")
|
|
state.reset()
|
|
expect(runtime.initialize(clock, state, 806042).get(&"valid", false), "initialize observation")
|
|
clock.set_paused(true)
|
|
tree.root.size = Vector2i(1440, 810)
|
|
layout = OS.get_environment("P23_LAYOUT")
|
|
expect(layout in ["A", "B"], "declared layout A or B")
|
|
main = load("res://scenes/main/main.tscn").instantiate()
|
|
main.manual_save_path = "user://saves/manual_ui"
|
|
if role == "reader":
|
|
import_package(directory.path_join("formal"), String(main.manual_save_path))
|
|
tree.root.add_child(main)
|
|
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
stream = Stream.new()
|
|
expect(stream.open(directory.path_join(role + "-stream")), "fresh observation stream")
|
|
stream.watch(main.world_preview)
|
|
await frames()
|
|
|
|
func command(id: StringName, payload: Dictionary) -> bool:
|
|
var result: CommandResult = runtime.submit_command(id, payload)
|
|
expect(result.is_success(), String(id) + ": " + str(result.code))
|
|
expect(stream.record("command", {&"command": id, &"payload": payload,
|
|
&"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command")
|
|
return result.is_success()
|
|
|
|
func advance(minutes: int) -> void:
|
|
for unused: int in range(minutes):
|
|
clock.advance_minutes(1)
|
|
await frames()
|
|
await observe_progress()
|
|
if clock.absolute_minute % 30 == 0:
|
|
expect(stream.capture("minute-%d" % clock.absolute_minute, runtime, main.world_preview), "sparse minute observation")
|
|
|
|
func observe_progress() -> void:
|
|
pass
|
|
|
|
func prepare() -> void:
|
|
# Both layouts receive the same definition-derived diagnostic construction budget.
|
|
# Cafeteria/kitchen move together to x=6, leaving x=4..5 for the declared B EV.
|
|
var builds: Array = [[&"reception", Vector3i(-8, 0, 0)],
|
|
[&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)],
|
|
[&"kitchen", Vector3i(6, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)],
|
|
[&"standard_room", Vector3i(-8, 2, 0)], [&"guest_stairs", Vector3i(-4, 1, 0)],
|
|
[&"guest_stairs", Vector3i(-4, 2, 0)], [&"guest_corridor", Vector3i(-5, 2, 0)],
|
|
[&"guest_corridor", Vector3i(-6, 2, 0)], [&"guest_elevator", Vector3i.ZERO],
|
|
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)],
|
|
[&"staff_waiting_room", Vector3i(-4, 0, 1)]]
|
|
var improvement: Dictionary = Comparison.new().declaration()[&"improvement"]
|
|
var budget_builds := builds.duplicate(true)
|
|
budget_builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
|
var needed := {&"gold": 0, &"materials": 0}
|
|
var costs: Array = []
|
|
for row: Array in budget_builds:
|
|
var definition: FacilityDefinition = runtime.definition_registry.get_facility(row[0])
|
|
costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)})
|
|
for key: StringName in needed:
|
|
needed[key] += int(definition.construction_cost.get(key, 0))
|
|
var state: Variant = tree.root.get_node("GameState")
|
|
var deltas := {}
|
|
for key: StringName in needed:
|
|
if needed[key] > state.get_resource(key):
|
|
deltas[key] = needed[key] - state.get_resource(key)
|
|
budget = {"setup": "diagnostic", "costs": costs, "needed": needed,
|
|
"initial": state.resources.duplicate(true), "deltas": deltas}
|
|
if not deltas.is_empty(): command(&"change_resources", {&"deltas": deltas})
|
|
expect(stream.record("budget", budget), "common diagnostic budget")
|
|
if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]])
|
|
# Preserve the supply scenario's staged support/construction sequence.
|
|
for index: int in range(builds.size()):
|
|
if index == 5: await advance(180)
|
|
if index == 7: await advance(60)
|
|
if index == 8: await advance(10)
|
|
command(&"build_facility", {&"definition_id": builds[index][0], &"origin": builds[index][1]})
|
|
var transport_minutes := 0
|
|
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
|
transport_minutes = maxi(transport_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
|
await advance(maxi(10, transport_minutes))
|
|
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
|
facility_ids[facility.definition_id] = facility.instance_id
|
|
command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"],
|
|
&"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}})
|
|
for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"],
|
|
[&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]:
|
|
command(&"hire_staff_candidate", {&"candidate_id": assignment[0]})
|
|
command(&"set_staff_work_assignment", {&"staff_id": assignment[0],
|
|
&"facility_id": facility_ids[assignment[1]], &"template_id": &"DAY"})
|
|
command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"],
|
|
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40})
|
|
command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"],
|
|
&"enabled": true, &"target_stock": 8})
|
|
# The OS observation uses the already declared transport cohort, plus
|
|
# ordinary public arrivals whose service choice remains Runtime-owned.
|
|
# These commands are recorded and identical across A/B; no actor is moved,
|
|
# despawned, or inserted into a room/queue by the fixture.
|
|
var demand: Dictionary = Comparison.new().declaration()
|
|
expect(stream.record("observation-demand", {"cohort": demand[&"arrivals"],
|
|
"schedule": "all observation actors at the recorded current minute; offsets apply only to native layout comparison",
|
|
"minute": clock.absolute_minute}), "declared observation demand")
|
|
for arrival: Dictionary in demand[&"arrivals"]:
|
|
command(&"spawn_guest", {&"guest_id": StringName("os-" + String(arrival[&"id"])),
|
|
&"guest_type_id": &"traveler", &"gold": 100,
|
|
&"options": {&"current_cell": Comparison.START, &"state": GuestRuntime.State.MOVING,
|
|
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
|
&"path_handle": {&"target_cell": Comparison.GOAL}}})
|
|
command(&"spawn_guest", {&"guest_id": StringName("service-" + String(arrival[&"id"])),
|
|
&"guest_type_id": &"traveler", &"gold": 100})
|
|
await advance(120)
|
|
|
|
func layer_observations(prefix: String) -> void:
|
|
await button(main.front_button)
|
|
expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report")
|
|
await button(main.back_button)
|
|
expect(stream.capture(prefix + "-BACK", runtime, main.world_preview), "BACK state/report")
|
|
|
|
func writer() -> void:
|
|
await prepare()
|
|
await layer_observations("writer")
|
|
capture("presave")
|
|
await button(main.find_child("ManualSaveButton", true, false))
|
|
expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save")
|
|
capture("checkpoint")
|
|
export_package(String(main.manual_save_path), "formal")
|
|
var formal := inspect_formal()
|
|
var presave := FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var"))
|
|
expect(presave == var_to_bytes(formal), "whole presave formal model")
|
|
expect(not operation_results.is_empty(), "save result recorded")
|
|
if not operation_results.is_empty():
|
|
verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED")
|
|
var members := {}
|
|
for member: String in MEMBERS:
|
|
members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member))
|
|
json_file("save.json", {"pid": OS.get_process_id(), "members": members})
|
|
|
|
func reader(distinct: bool) -> void:
|
|
var saved: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("save.json")))
|
|
expect(distinct and saved.pid != OS.get_process_id(), "distinct actual reader PID")
|
|
await load_menu(main.find_child("SaveLoadMenu", true, false))
|
|
await frames()
|
|
expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume")
|
|
capture("checkpoint")
|
|
var formal := inspect_formal()
|
|
expect(FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var")) == var_to_bytes(formal), "whole formal resume model")
|
|
for member: String in MEMBERS:
|
|
expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "formal member " + member)
|
|
expect(not operation_results.is_empty(), "load result recorded")
|
|
if not operation_results.is_empty():
|
|
verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED")
|
|
await layer_observations("reader")
|
|
|
|
func finish_observations() -> void:
|
|
stream.close()
|