105 lines
5.5 KiB
GDScript
105 lines
5.5 KiB
GDScript
extends RefCounted
|
|
## Public-command experiment; no synthetic state, costs or capacities.
|
|
## Predeclared metric: total destination-arrival minutes of the entire cohort.
|
|
## B must be strictly smaller. Incomplete cohorts cannot establish improvement.
|
|
const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd")
|
|
const SEED := 806042
|
|
const WINDOW := 480
|
|
const START := Vector3i(2, 0, 0)
|
|
const GOAL := Vector3i(2, 1, 0)
|
|
var stream: RefCounted
|
|
|
|
func declaration() -> Dictionary:
|
|
var rng := RandomNumberGenerator.new()
|
|
rng.seed = SEED
|
|
var arrivals: Array[Dictionary] = []
|
|
for index: int in range(48):
|
|
arrivals.append({&"id": StringName("p23-%03d" % index),
|
|
&"offset": (index / 4) * 15 + rng.randi_range(0, 2)})
|
|
arrivals.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
|
return a[&"offset"] < b[&"offset"] or (a[&"offset"] == b[&"offset"] and String(a[&"id"]) < String(b[&"id"])))
|
|
return {&"seed": SEED, &"initial_clock": [1, 6, 0], &"budget": &"GameState.reset",
|
|
&"production": &"Runtime.initialize defaults; production operations not enabled",
|
|
&"arrivals": arrivals, &"from": START, &"to": GOAL, &"window": WINDOW,
|
|
&"metric": &"sum_destination_arrival_minutes_entire_cohort",
|
|
&"improvement": {&"definition_id": &"guest_elevator", &"origin": Vector3i(4, 0, 0)}}
|
|
|
|
func run(tree: SceneTree, directory: String, layout: String) -> bool:
|
|
if layout not in ["A", "B"]: return false
|
|
var global_clock := tree.root.get_node_or_null("GameClock")
|
|
if global_clock != null: global_clock.set_paused(true)
|
|
stream = Stream.new()
|
|
if not stream.open(directory): return false
|
|
var input := declaration()
|
|
if not stream.record("declaration", input):
|
|
stream.close()
|
|
return false
|
|
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
|
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
|
var state: Variant = load("res://autoload/game_state.gd").new()
|
|
clock.reset(1, 6, 0)
|
|
state.reset()
|
|
var ok: bool = runtime.initialize(clock, state, SEED)[&"valid"]
|
|
clock.set_paused(true)
|
|
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
ok = stream.record("initial", runtime.create_snapshot()) and ok
|
|
var builds: Array = [[&"guest_elevator", Vector3i.ZERO],
|
|
[&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)]]
|
|
if layout == "B": builds.append([input[&"improvement"][&"definition_id"], input[&"improvement"][&"origin"]])
|
|
var construction_minutes := 0
|
|
# Identical preparation time derived from the same registered definitions.
|
|
for id: StringName in [&"guest_elevator", &"guest_corridor"]:
|
|
construction_minutes = maxi(construction_minutes, runtime.definition_registry.get_facility(id).build_minutes)
|
|
for index: int in range(builds.size()):
|
|
var payload := {&"definition_id": builds[index][0], &"origin": builds[index][1]}
|
|
var result: CommandResult = runtime.submit_command(&"build_facility", payload,
|
|
&"player", &"", 0, clock.absolute_minute, StringName("p23.build.%d" % index))
|
|
ok = stream.record("build-%d" % index, {&"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
|
clock.advance_minutes(construction_minutes + 1)
|
|
ok = stream.record("prepared", runtime.create_snapshot()) and ok
|
|
runtime.domain_events.subscribe(&"*", &"p23.capture", Callable(self, "_event"))
|
|
var view := WorldPreview.new()
|
|
view.size = Vector2(1440, 810)
|
|
view.set_runtime(runtime)
|
|
tree.root.add_child(view)
|
|
stream.watch(view)
|
|
var finished: Dictionary = {}
|
|
for offset: int in range(WINDOW + 1):
|
|
if not ok: break
|
|
if offset > 0: clock.advance_minutes(1)
|
|
for arrival: Dictionary in input[&"arrivals"]:
|
|
if arrival[&"offset"] != offset: continue
|
|
var payload := {&"guest_id": arrival[&"id"], &"guest_type_id": &"traveler", &"gold": 100,
|
|
&"options": {&"current_cell": START, &"state": GuestRuntime.State.MOVING,
|
|
&"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000,
|
|
&"path_handle": {&"target_cell": GOAL}}}
|
|
var result: CommandResult = runtime.submit_command(&"spawn_guest", payload,
|
|
&"world", arrival[&"id"], 10, -1, StringName("p23.spawn.%s" % arrival[&"id"]))
|
|
ok = stream.record("arrival-%s" % arrival[&"id"], {&"offset": offset, &"payload": payload, &"result": result.to_dictionary()}) and result.is_success() and ok
|
|
# Typed per-minute projections retain paths, queues, density and actual
|
|
# completed services. Whole aggregate snapshots are only sampled sparsely.
|
|
var guest_records: Dictionary = runtime.guest_service.create_snapshot()
|
|
ok = stream.record("tick-%03d" % offset, {&"minute": clock.absolute_minute,
|
|
&"guests": guest_records, &"path": runtime.path_service.create_snapshot(),
|
|
&"elevators": runtime.elevators.create_snapshot(), &"facilities": runtime.facility_service.create_snapshot()}) and ok
|
|
for guest: Dictionary in guest_records[&"guests"]:
|
|
if guest[&"current_cell"] != [GOAL.x, GOAL.y, GOAL.z] or finished.has(guest[&"guest_id"]): continue
|
|
finished[guest[&"guest_id"]] = offset
|
|
if offset % 30 == 0:
|
|
view.queue_redraw()
|
|
await tree.process_frame
|
|
await tree.process_frame
|
|
ok = stream.capture("frame-%03d" % offset, runtime, view) and ok
|
|
ok = stream.record("first-arrivals", {&"offsets": finished}) and ok
|
|
ok = stream.record("final", runtime.create_snapshot()) and ok
|
|
stream.close()
|
|
view.free()
|
|
runtime.free()
|
|
clock.free()
|
|
state.free()
|
|
return ok
|
|
|
|
func _event(event: SimulationDomainEvent) -> void:
|
|
stream.record("event", {&"type": event.event_type, &"minute": event.absolute_minute,
|
|
&"entities": event.entity_ids, &"payload": event.payload.duplicate(true)})
|