Files
TheTree_mock/tests/performance/profile_production_scene.gd
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

139 lines
6.8 KiB
GDScript

extends SceneTree
## Diagnostic measurement, not a replacement for full-load acceptance.
var phase_samples: Dictionary = {}
var phase_start := 0
var current_phase := -1
func finish_phase() -> void:
if current_phase >= 0:
if not phase_samples.has(current_phase): phase_samples[current_phase] = []
phase_samples[current_phase].append(Time.get_ticks_usec()-phase_start)
current_phase = -1
func begin_phase(phase: int, _minute: int) -> void:
finish_phase()
current_phase = phase
phase_start = Time.get_ticks_usec()
const Demo := preload("res://scripts/ui/pixel_tower_demo.gd")
func _initialize() -> void: call_deferred("run")
func stats(samples: Array[int]) -> Dictionary:
var sorted := samples.duplicate()
sorted.sort()
var total := 0
for value: int in sorted: total += value
return {"samples":sorted.size(),"average_us":float(total)/sorted.size(),"p95_us":sorted[ceili(sorted.size()*0.95)-1],"max_us":sorted.back()}
func run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
clock.reset(1,6,0);state.reset();state.resources[&"gold"] += 300;runtime.initialize(clock,state,424242);clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var demo := Demo.new()
if not await demo.prepare(runtime,clock,self): quit(1);return
var main: Control = load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
await process_frame;await process_frame
clock.tick_phase_started.connect(begin_phase)
clock.tick_completed.connect(func(_minute: int): finish_phase())
var checkpoint: Dictionary = runtime.create_snapshot()
var report := {"fixture":demo.describe(),"headless":DisplayServer.get_name()=="headless","limits":{"x1_average_us":4000,"x3_p95_us":12000},"measurements":{}}
for speed: int in [1,3]:
if not runtime.restore_snapshot(checkpoint):quit(2);return
phase_samples.clear()
var times: Array[int] = []
for frame in 40:
var minute: int = clock.absolute_minute
var start := Time.get_ticks_usec()
clock.advance_minutes(speed)
# Include deferred dashboard cost in the same sample, rather than
# hiding work outside the synchronous timing interval.
if main.has_method("_flush_dashboard_refresh"):
main.call("_flush_dashboard_refresh")
times.append(Time.get_ticks_usec()-start)
if clock.absolute_minute != minute+speed:quit(3);return
await process_frame
report["measurements"]["x%d" % speed] = stats(times)
var phases := {}
for phase: int in phase_samples:
var values: Array[int] = []
values.assign(phase_samples[phase])
phases[SimulationKernel.TickPhase.keys()[phase]] = stats(values)
report["measurements"]["x%d" % speed]["phases"] = phases
# Distinguish full update cost from serialization and digest micro-costs.
var snapshots: Array[int] = []
var digests: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
var snapshot: Dictionary = runtime.create_snapshot()
snapshots.append(Time.get_ticks_usec()-start)
start=Time.get_ticks_usec()
SimulationDigest.compute(snapshot)
digests.append(Time.get_ticks_usec()-start)
var section_costs := {}
var final_snapshot: Dictionary = runtime.create_snapshot()
for section: Variant in final_snapshot[&"systems"][&"runtime"]:
var values: Array[int] = []
for attempt in 5:
var start := Time.get_ticks_usec()
SimulationDigest.compute(final_snapshot[&"systems"][&"runtime"][section])
values.append(Time.get_ticks_usec()-start)
section_costs[String(section)] = stats(values)
report["runtime_section_digests"] = section_costs
var ui_costs := {}
for method: StringName in [&"_refresh_dashboard", &"_refresh_staff_candidates", &"_refresh_selected_entity", &"_operations_dashboard_text"]:
var values: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
main.call(method)
values.append(Time.get_ticks_usec()-start)
ui_costs[String(method)] = stats(values)
if main.opening_guide != null:
for method: StringName in [&"refresh", &"_read_progress", &"_first_lodging_sale"]:
var values: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
main.opening_guide.call(method)
values.append(Time.get_ticks_usec()-start)
ui_costs["opening_guide."+String(method)] = stats(values)
report["ui_refresh_costs"] = ui_costs
var validation_costs := {}
for service_name: StringName in [&"spatial_subslot_service", &"route_subslot_path_adapter", &"facility_infrastructure_projector", &"excavation_service"]:
var values: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
var result: Dictionary = runtime.get(service_name).validate_invariants()
values.append(Time.get_ticks_usec()-start)
if not result.get(&"ok", false):quit(5);return
validation_costs[String(service_name)] = stats(values)
report["infrastructure_validation_costs"] = validation_costs
var snapshot_costs := {}
for service_name: StringName in [&"domain_events", &"command_queue", &"world_service", &"path_service", &"construction_service", &"facility_service", &"guest_service", &"staff_service", &"operations_coordinator", &"facility_infrastructure_projector", &"route_subslot_path_adapter"]:
var values: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
runtime.get(service_name).create_snapshot()
values.append(Time.get_ticks_usec()-start)
snapshot_costs[String(service_name)] = stats(values)
var copies: Array[int] = []
for attempt in 20:
var start := Time.get_ticks_usec()
final_snapshot.duplicate(true)
copies.append(Time.get_ticks_usec()-start)
snapshot_costs["whole_snapshot_deep_copy"] = stats(copies)
report["snapshot_component_costs"] = snapshot_costs
report["snapshot"] = stats(snapshots)
report["digest"] = stats(digests)
report["within_budget"] = report["measurements"]["x1"]["average_us"] <= 4000 and report["measurements"]["x3"]["p95_us"] <= 12000
report["limitations"] = "Small disposable production scenario; auto-save disabled; synchronous tick includes UI subscribers, excludes frame rendering and GPU. Not full-load or Windows acceptance."
var path := OS.get_environment("THE_TREE_RUNTIME_PROFILE_PATH")
if path.is_empty():path="res://artifacts/facility-dimensions-2026-09-29/production-scene-profile.json"
var file := FileAccess.open(path,FileAccess.WRITE)
if file == null:quit(4);return
file.store_string(JSON.stringify(report," ")+"\n");file.close()
print("PRODUCTION_PROFILE ",JSON.stringify(report["measurements"])," snapshot=",report["snapshot"]," digest=",report["digest"]," within_budget=",report["within_budget"])
main.queue_free();await process_frame
quit(0)