Files
TheTree_mock/tests/fixtures/p23-03/observation.gd
T

194 lines
11 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")
const PurchaseQuoteV2 := preload("res://scripts/supply/supply_purchase_quote_v2.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(payload) + ": " + 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(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
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 runtime-derived budget for all setup spends.
# 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 assignments: Array = [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"],
[&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]
var purchase_quantity := 40
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 construction_total := needed.duplicate(true)
var signing_costs: Array = []
var signing_total := 0
for assignment: Array in assignments:
var candidate: Dictionary = runtime.mvp_content_catalog.get_recruitment_entry(assignment[0])
expect(candidate.has(&"signing_cost"), "registered signing cost " + String(assignment[0]))
var signing_cost := int(candidate[&"signing_cost"])
signing_costs.append({&"candidate_id": assignment[0], &"signing_cost": signing_cost,
&"source": "MvpContentCatalog.get_recruitment_entry.signing_cost"})
signing_total += signing_cost
needed[&"gold"] += signing_total
# Before warehouse construction, use the purchase service's catalog/quote
# branch. Check this total against its live preview immediately before buying.
var item := SupplyCatalog.get_item(SupplyCatalog.RAW_FOOD)
var purchase_quote := PurchaseQuoteV2.make(SupplyCatalog.RAW_FOOD, purchase_quantity, int(item[&"unit_price"]))
expect(not purchase_quote.is_empty(), "registered raw-food purchase quote")
var purchase_total := int(purchase_quote[&"effective_total"] if PurchaseQuoteV2.eligible(runtime) else purchase_quote[&"base_total"])
needed[&"gold"] += purchase_total
var purchase_cost := {&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity,
&"unit_price": item[&"unit_price"], &"quote": purchase_quote, &"total_cost": purchase_total,
&"source": "SupplyCatalog.unit_price / PurchaseQuoteV2.make; preview_purchase eligibility"}
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,
"construction_total": construction_total, "signing_costs": signing_costs,
"signing_total": signing_total, "purchase_cost": purchase_cost}
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 assignments:
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"})
var actual_quote: Dictionary = runtime.preview_supply_purchase(facility_ids[&"warehouse"], SupplyCatalog.RAW_FOOD, purchase_quantity)
expect(int(actual_quote[&"total_cost"]) == purchase_total, "budget matches actual purchase quote")
expect(stream.record("setup-purchase-quote", actual_quote), "record actual setup purchase quote")
command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"],
&"item_id": SupplyCatalog.RAW_FOOD, &"quantity": purchase_quantity})
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(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report")
await button(main.back_button)
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
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()