Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
extends PathService
|
||||
|
||||
# Pre-optimization algorithm, retained as an exact route-order oracle.
|
||||
func _run_a_star(
|
||||
start: Vector3i,
|
||||
goal: Vector3i,
|
||||
allowed_layers: Array[int],
|
||||
allowed_access_ids: Array[StringName]
|
||||
) -> Array[Vector3i]:
|
||||
if start == goal:
|
||||
return [start]
|
||||
|
||||
var open_set: Dictionary = {start: true}
|
||||
var closed_set: Dictionary = {}
|
||||
var came_from: Dictionary = {}
|
||||
var g_score: Dictionary = {start: 0}
|
||||
|
||||
while not open_set.is_empty():
|
||||
var current := _select_best_open_node(open_set, g_score, goal)
|
||||
if current == goal:
|
||||
return _reconstruct_path(came_from, current)
|
||||
|
||||
open_set.erase(current)
|
||||
closed_set[current] = true
|
||||
var edges: Array = _adjacency.get(current, [])
|
||||
for edge_value: Variant in edges:
|
||||
var edge: Dictionary = edge_value
|
||||
var neighbor: Vector3i = edge[&"to"]
|
||||
if neighbor.z not in allowed_layers \
|
||||
or _cell_is_temporarily_blocked(neighbor) \
|
||||
or not _edge_meets_vertical_policy(current, edge) \
|
||||
or not _edge_is_accessible(edge, allowed_access_ids):
|
||||
continue
|
||||
var tentative_cost: int = int(g_score[current]) + _edge_total_cost(edge)
|
||||
var known_cost: int = int(g_score.get(neighbor, _INFINITE_COST))
|
||||
var better_parent := false
|
||||
if tentative_cost == known_cost and came_from.has(neighbor):
|
||||
better_parent = _coordinate_less(current, came_from[neighbor])
|
||||
if tentative_cost > known_cost or (tentative_cost == known_cost and not better_parent):
|
||||
continue
|
||||
|
||||
came_from[neighbor] = current
|
||||
g_score[neighbor] = tentative_cost
|
||||
if closed_set.has(neighbor):
|
||||
closed_set.erase(neighbor)
|
||||
open_set[neighbor] = true
|
||||
|
||||
return []
|
||||
|
||||
|
||||
func _select_best_open_node(open_set: Dictionary, g_score: Dictionary, goal: Vector3i) -> Vector3i:
|
||||
var best := Vector3i.ZERO
|
||||
var has_best := false
|
||||
var best_score := _INFINITE_COST
|
||||
for candidate_value: Variant in open_set:
|
||||
var candidate: Vector3i = candidate_value
|
||||
var score: int = int(g_score[candidate]) + _heuristic(candidate, goal)
|
||||
if not has_best or score < best_score:
|
||||
best = candidate
|
||||
best_score = score
|
||||
has_best = true
|
||||
elif score == best_score and _coordinate_less(candidate, best):
|
||||
best = candidate
|
||||
return best
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0fbg8t52lexf
|
||||
@@ -0,0 +1,56 @@
|
||||
extends WorldPreview
|
||||
|
||||
# Uncached geometry and reflective service lookup oracle.
|
||||
func get_map_rect() -> Rect2:
|
||||
var bounds := _world_bounds()
|
||||
if bounds.is_empty():
|
||||
return Rect2()
|
||||
var dimensions := _cell_dimensions()
|
||||
var map_size := Vector2(
|
||||
int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1,
|
||||
int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1
|
||||
) * dimensions
|
||||
return Rect2(((size - map_size) * 0.5 + pan_offset).floor(), map_size)
|
||||
|
||||
|
||||
func _cell_dimensions() -> Vector2:
|
||||
var bounds := _world_bounds()
|
||||
if bounds.is_empty():
|
||||
return Vector2(32, 48)
|
||||
var columns := maxi(int(bounds[&"max_x"]) - int(bounds[&"min_x"]) + 1, 1)
|
||||
var rows := maxi(int(bounds[&"max_y"]) - int(bounds[&"min_y"]) + 1, 1)
|
||||
var available := (size - MAP_PADDING * 2.0).max(Vector2.ONE)
|
||||
var fit := minf(available.x / (columns * 16.0), available.y / (rows * 24.0))
|
||||
# Native 16px units use integer magnification. Large worlds can be viewed
|
||||
# at overview scale, then enlarged with the same invertible camera mapping.
|
||||
var pixel := maxf(floorf(fit), 1.0) if fit >= 1.0 else maxf(fit, 0.25)
|
||||
return (Vector2(16, 24) * pixel * zoom_level).round().max(Vector2(4, 6))
|
||||
|
||||
|
||||
func _service(property_name: StringName) -> Variant:
|
||||
if _runtime == null or not _runtime is Object:
|
||||
return null
|
||||
for property: Dictionary in _runtime.get_property_list():
|
||||
if StringName(property.get(&"name", &"")) == property_name:
|
||||
return _runtime.get(property_name)
|
||||
return null
|
||||
|
||||
|
||||
func _world_bounds() -> Dictionary:
|
||||
var world: Variant = _service(&"world_service")
|
||||
if world == null or not world.has_method(&"get_bounds"):
|
||||
return {}
|
||||
var result: Variant = world.call(&"get_bounds")
|
||||
if not result is Dictionary:
|
||||
return {}
|
||||
for key: StringName in [&"min_x", &"max_x", &"min_y", &"max_y"]:
|
||||
if not result.has(key) and not result.has(String(key)):
|
||||
return {}
|
||||
return {
|
||||
&"min_x": int(result.get(&"min_x", result.get("min_x", 0))),
|
||||
&"max_x": int(result.get(&"max_x", result.get("max_x", -1))),
|
||||
&"min_y": int(result.get(&"min_y", result.get("min_y", 0))),
|
||||
&"max_y": int(result.get(&"max_y", result.get("max_y", -1))),
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://df7jpvnx5bhb
|
||||
@@ -28,7 +28,7 @@ const FULL_TARGET := {
|
||||
"unfinished_logistics": 200,
|
||||
"active_events": 50,
|
||||
"minutes": 1_440,
|
||||
"speed": 4,
|
||||
"speed": 3,
|
||||
}
|
||||
|
||||
const SMOKE_TARGET := {
|
||||
@@ -40,7 +40,7 @@ const SMOKE_TARGET := {
|
||||
"unfinished_logistics": 20,
|
||||
"active_events": 5,
|
||||
"minutes": 1_440,
|
||||
"speed": 4,
|
||||
"speed": 3,
|
||||
}
|
||||
|
||||
const STALE_BACKLOG_LIMIT_MINUTES := 120
|
||||
@@ -144,14 +144,14 @@ func run(tier: StringName = &"smoke") -> Dictionary:
|
||||
var simulation_duration_us := Time.get_ticks_usec() - simulation_started
|
||||
# Feed raw path samples after timing the simulation. PerformanceBudget returns
|
||||
# a full percentile report per record, so doing this inside each path callback
|
||||
# would make the profiler itself dominate the x4 measurement.
|
||||
# would make the profiler itself dominate the x3 measurement.
|
||||
for duration_us: int in _path_duration_samples:
|
||||
profiler.record_sample(&"path_query", duration_us)
|
||||
|
||||
report["simulation"] = {
|
||||
"requested_minutes": int(target["minutes"]),
|
||||
"completed_minutes": completed_minutes,
|
||||
"x4_frames": int(ceil(float(completed_minutes) / 4.0)),
|
||||
"x3_frames": int(ceil(float(completed_minutes) / 3.0)),
|
||||
"duration_ms": simulation_duration_us / 1_000.0,
|
||||
"game_minutes_per_real_second": (
|
||||
float(completed_minutes) * 1_000_000.0 / float(maxi(simulation_duration_us, 1))
|
||||
@@ -816,10 +816,10 @@ func _run_validations(
|
||||
_add_validation(validations, &"path_queries_reachable", _path_query_failures == 0, {"failures": _path_query_failures})
|
||||
|
||||
_validate_snapshot_round_trips(validations, services, cells)
|
||||
var x4_report: Dictionary = profiler.get_report(&"simulation_x4")
|
||||
var x3_report: Dictionary = profiler.get_report(&"simulation_x3")
|
||||
var path_report: Dictionary = profiler.get_report(&"path_query")
|
||||
var stall_report: Dictionary = profiler.get_report(&"main_thread_stall")
|
||||
_add_validation(validations, &"simulation_x4_budget", not bool(x4_report[&"budget_exceeded"]), _primitive_dictionary(x4_report))
|
||||
_add_validation(validations, &"simulation_x3_budget", not bool(x3_report[&"budget_exceeded"]), _primitive_dictionary(x3_report))
|
||||
_add_validation(validations, &"path_query_budget", not bool(path_report[&"budget_exceeded"]), _primitive_dictionary(path_report))
|
||||
_add_validation(validations, &"main_thread_stall_budget", not bool(stall_report[&"budget_exceeded"]), _primitive_dictionary(stall_report))
|
||||
|
||||
@@ -906,7 +906,7 @@ func _count_terminal_logistics() -> int:
|
||||
|
||||
func _performance_report(profiler: Variant) -> Dictionary:
|
||||
return {
|
||||
"simulation_x4": _primitive_dictionary(profiler.get_report(&"simulation_x4")),
|
||||
"simulation_x3": _primitive_dictionary(profiler.get_report(&"simulation_x3")),
|
||||
"path_query": _primitive_dictionary(profiler.get_report(&"path_query")),
|
||||
"main_thread_stall": _primitive_dictionary(profiler.get_report(&"main_thread_stall")),
|
||||
"domain_timings_per_game_minute": {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
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")
|
||||
clock.reset(1,6,0);state.reset();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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cd34rvbc7t447
|
||||
@@ -13,6 +13,8 @@ func _initialize() -> void:
|
||||
|
||||
func _run() -> void:
|
||||
print("Simulation digest: v1 byte compatibility and growing-history benchmark")
|
||||
_test_text_chunk_boundaries()
|
||||
_test_scalar_cache_boundaries()
|
||||
_test_value_types()
|
||||
_test_dictionary_layouts()
|
||||
_test_mutable_values()
|
||||
@@ -220,3 +222,33 @@ func _expect(condition: bool, label: String) -> void:
|
||||
else:
|
||||
_failures += 1
|
||||
push_error(" FAIL: %s" % label)
|
||||
|
||||
|
||||
func _test_scalar_cache_boundaries() -> void:
|
||||
var values: Array = []
|
||||
for i in 700:
|
||||
values.append(i-350)
|
||||
values.append("entry_%d" % i)
|
||||
values.append(StringName("entry_%d" % i))
|
||||
values.append("x".repeat(129))
|
||||
values.append(StringName("x".repeat(129)))
|
||||
values.append("utf8_" + String.chr(0x1f333))
|
||||
values.append(values.duplicate())
|
||||
_expect_bytes(values, "bounded scalar cache saturation preserves exact canonical bytes")
|
||||
_expect_bytes([1,"1",&"1",1,"1",&"1"], "scalar cache preserves int string and name type tags")
|
||||
var previous := Digest.compute(values)
|
||||
values[0] = -999999
|
||||
_expect_bytes(values, "new digest reads changed input after prior cache lifetime")
|
||||
_expect(Digest.compute(values) != previous, "per-call scalar caches never retain previous input")
|
||||
|
||||
|
||||
func _test_text_chunk_boundaries() -> void:
|
||||
for count: int in [63,64,65,129]:
|
||||
var strings: Array[String] = []
|
||||
var names: Array[StringName] = []
|
||||
for index in count:
|
||||
var text := "field_%d:" % index + String.chr(0x1f333) + String.chr(0) + "end"
|
||||
strings.append(text)
|
||||
names.append(StringName(text))
|
||||
_expect_bytes(strings, "UTF8 and NUL string chunk boundary %d" % count)
|
||||
_expect_bytes(names, "StringName chunk boundary %d" % count)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
extends SceneTree
|
||||
const Demo := preload("res://scripts/ui/pixel_tower_demo.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
var runtime: Variant
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok: failures += 1; push_error(label)
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
func compare(label: String) -> void:
|
||||
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
||||
expect(runtime.is_facility_reachable(facility.instance_id) == bool(runtime.get_facility_access_report(facility.instance_id)[&"reachable"]), label + ": " + String(facility.instance_id))
|
||||
func run() -> void:
|
||||
runtime = root.get_node("SimulationRuntime")
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
var state: Variant = root.get_node("GameState")
|
||||
clock.reset(1,6,0); state.reset(); runtime.initialize(clock,state,424242); clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
|
||||
var demo := Demo.new()
|
||||
expect(await demo.prepare(runtime,clock,self), "prepare actual multi-floor topology")
|
||||
if failures: quit(failures); return
|
||||
compare("connected topology")
|
||||
expect(not runtime.is_facility_reachable(&"missing"), "missing facility is unreachable")
|
||||
var checkpoint: Dictionary = runtime.create_snapshot()
|
||||
var facility: FacilityInstance = runtime.facility_service.get_instances()[0]
|
||||
facility.access_available = false
|
||||
expect(not runtime.is_facility_reachable(facility.instance_id), "operating access override is honored")
|
||||
expect(runtime.restore_snapshot(checkpoint), "restore access override")
|
||||
# Block every graph cell, including all candidate public entrances. Do not
|
||||
# leave a second entrance that can legitimately reach the same destination.
|
||||
for cell: Vector3i in runtime.world_service.get_walkable_cells():
|
||||
if runtime.path_service.has_cell(cell): runtime.path_service.set_cell_temporary_block(cell,true)
|
||||
compare("blocked topology")
|
||||
for item: FacilityInstance in runtime.facility_service.get_instances():
|
||||
expect(not runtime.is_facility_reachable(item.instance_id), "blocked entrance cannot report reachable")
|
||||
expect(runtime.restore_snapshot(checkpoint), "restore graph after temporary blocks")
|
||||
compare("restored topology")
|
||||
var measurements := {}
|
||||
for method: StringName in [&"get_facility_access_report", &"is_facility_reachable"]: measurements[String(method)] = []
|
||||
for attempt in 100:
|
||||
var methods: Array[StringName] = [&"get_facility_access_report", &"is_facility_reachable"]
|
||||
if attempt % 2: methods.reverse()
|
||||
for method: StringName in methods:
|
||||
var start := Time.get_ticks_usec()
|
||||
for item: FacilityInstance in runtime.facility_service.get_instances(): runtime.call(method,item.instance_id)
|
||||
measurements[String(method)].append(Time.get_ticks_usec()-start)
|
||||
var output := OS.get_environment("THE_TREE_REACHABILITY_PROFILE")
|
||||
if not output.is_empty():
|
||||
var file := FileAccess.open(output,FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(measurements," "));file.close()
|
||||
print("Facility reachability: %d checks, %d failures" % [checks,failures]);quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b87cjkd3d5k37
|
||||
@@ -0,0 +1,39 @@
|
||||
extends SceneTree
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
func _initialize() -> void:call_deferred("run")
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks+=1
|
||||
if not ok:failures+=1;push_error(label)
|
||||
func run() -> void:
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
var state: Variant = root.get_node("GameState")
|
||||
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,42515);clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
|
||||
var baseline: Dictionary = runtime.create_snapshot()
|
||||
var notices: Array[int] = []
|
||||
runtime.state_updated.connect(func(_snapshot: Dictionary):notices.append(clock.absolute_minute))
|
||||
clock.advance_one_minute()
|
||||
expect(runtime.get("_latest_digest_dirty"),"no digest consumer leaves computation pending")
|
||||
var snapshot: Dictionary=runtime.latest_snapshot.duplicate(true)
|
||||
expect(runtime.latest_digest==SimulationDigest.compute(snapshot),"lazy digest matches exact published snapshot")
|
||||
expect(not runtime.get("_latest_digest_dirty"),"repeated reads reuse current publication digest")
|
||||
var prior: String=runtime.latest_digest
|
||||
clock.advance_one_minute()
|
||||
expect(runtime.get("_latest_digest_dirty") and runtime.latest_digest!=prior,"new publication invalidates old hash")
|
||||
expect(notices.size()==2,"lightweight notification emitted per publication")
|
||||
var lazy_end: Dictionary=runtime.create_snapshot()
|
||||
expect(runtime.restore_snapshot(baseline),"restore pre-run state")
|
||||
var observed: Array[Dictionary]=[]
|
||||
var observer := func(value: Dictionary,digest: String): observed.append({"snapshot":value.duplicate(true),"digest":digest})
|
||||
runtime.snapshot_published.connect(observer)
|
||||
clock.advance_minutes(2)
|
||||
expect(observed.size()==2,"legacy observer still receives every publication")
|
||||
for row: Dictionary in observed:
|
||||
expect(row["digest"]==SimulationDigest.compute(row["snapshot"]),"legacy observer receives exact eager hash")
|
||||
expect(runtime.create_snapshot()==lazy_end,"lazy and observed runs preserve identical simulation state")
|
||||
runtime.snapshot_published.disconnect(observer)
|
||||
clock.advance_one_minute()
|
||||
expect(runtime.get("_latest_digest_dirty"),"disconnecting legacy observer restores lazy computation")
|
||||
print("Lazy digest: %d checks, %d failures" % [checks,failures]);quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cyhrnv34kel3m
|
||||
@@ -0,0 +1,222 @@
|
||||
extends SceneTree
|
||||
## Regression for catalog manifest ownership, invalidation, legacy saves and restore.
|
||||
|
||||
class CountingCatalog:
|
||||
extends MvpContentCatalog
|
||||
var factory_calls := 0
|
||||
func build_all_guest_definitions() -> Array[GuestDefinition]:
|
||||
factory_calls += 1
|
||||
return super.build_all_guest_definitions()
|
||||
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
var runtime: Variant
|
||||
var clock: Variant
|
||||
var state: Variant
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error("MANIFEST: " + label)
|
||||
|
||||
func manifest(catalog: MvpContentCatalog) -> Dictionary:
|
||||
return runtime.call("_create_mvp_content_manifest", catalog)
|
||||
|
||||
# Independent original construction, including the existing full factory.
|
||||
# No ID list, original definition field, or class/species value is omitted.
|
||||
func original_manifest(catalog: MvpContentCatalog) -> Dictionary:
|
||||
var guest_ids: Array[String] = []
|
||||
for definition: GuestDefinition in catalog.build_all_guest_definitions():
|
||||
guest_ids.append(String(definition.guest_type_id))
|
||||
return {
|
||||
&"manifest_version": runtime.MVP_CONTENT_MANIFEST_VERSION,
|
||||
&"schema_version": MvpContentCatalog.SCHEMA_VERSION,
|
||||
&"guest_class_ids": strings(catalog.get_guest_class_ids()),
|
||||
&"species_ids": strings(catalog.get_species_ids()),
|
||||
&"staff_candidate_ids": strings(catalog.get_staff_candidate_ids()),
|
||||
&"unique_character_ids": strings(catalog.get_unique_character_ids()),
|
||||
&"guest_definition_ids": guest_ids,
|
||||
}
|
||||
|
||||
func strings(values: Array[StringName]) -> Array[String]:
|
||||
var result: Array[String] = []
|
||||
for value: StringName in values:
|
||||
result.append(String(value))
|
||||
return result
|
||||
|
||||
func documents() -> Dictionary:
|
||||
var result: Dictionary = {}
|
||||
for content_type: StringName in MvpContentCatalog.DEFAULT_PATHS:
|
||||
var file := FileAccess.open(MvpContentCatalog.DEFAULT_PATHS[content_type], FileAccess.READ)
|
||||
if file == null:
|
||||
return {}
|
||||
result[content_type] = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
return result
|
||||
|
||||
func same(left: Variant, right: Variant) -> bool:
|
||||
return runtime.call("_strict_variants_equal", left, right)
|
||||
|
||||
func check_manifest_shape(expected: Dictionary, actual: Dictionary, label: String) -> void:
|
||||
expect(actual.keys() == expected.keys(), label + " insertion order of manifest keys")
|
||||
for key: Variant in actual:
|
||||
expect(typeof(key) == TYPE_STRING_NAME, label + " key Variant type " + String(key))
|
||||
if expected[key] is Array:
|
||||
var values: Array = actual[key]
|
||||
expect(values.get_typed_builtin() == TYPE_STRING, label + " declared typed string Array " + String(key))
|
||||
var bytes := SimulationDigest.canonical_bytes(actual)
|
||||
expect(not bytes.is_empty() and bytes == SimulationDigest.canonical_bytes(expected), label + " nonempty canonical bytes")
|
||||
var digest := SimulationDigest.compute(actual)
|
||||
expect(not digest.is_empty() and digest == SimulationDigest.compute(expected), label + " nonempty exact digest")
|
||||
|
||||
func complete_live_guard() -> Dictionary:
|
||||
return {
|
||||
&"snapshot": runtime.create_snapshot(),
|
||||
&"clock_paused": clock.paused,
|
||||
&"save_policy": runtime.get_save_policy(),
|
||||
&"pending_checkpoint_count": runtime.get_pending_checkpoint_count(),
|
||||
&"last_daily_checkpoint_day": runtime.get_last_daily_checkpoint_day(),
|
||||
&"content_filter": runtime.event_service.get_content_filter_profile(),
|
||||
}
|
||||
|
||||
func run() -> void:
|
||||
clock = load("res://autoload/game_clock.gd").new()
|
||||
state = load("res://autoload/game_state.gd").new()
|
||||
runtime = load("res://autoload/simulation_runtime.gd").new()
|
||||
clock.reset(1,6,0)
|
||||
state.reset()
|
||||
expect(runtime.initialize(clock,state,840_101).get(&"valid", false), "canonical initialization")
|
||||
clock.set_paused(true)
|
||||
var cached: bool = runtime.has_method("_clear_mvp_content_manifest_cache")
|
||||
expect(cached, "production manifest cache is installed")
|
||||
var catalog := CountingCatalog.new()
|
||||
expect(catalog.rebuild().get(&"valid", false), "counting fixture uses unchanged real catalog")
|
||||
runtime.mvp_content_catalog = catalog
|
||||
var expected := original_manifest(catalog)
|
||||
catalog.factory_calls = 0
|
||||
var first := manifest(catalog)
|
||||
expect(same(expected, first), "first read preserves all values and generated ID ordering")
|
||||
check_manifest_shape(expected, first, "first fill")
|
||||
first[&"manifest_version"] = -1
|
||||
first[&"staff_candidate_ids"].clear()
|
||||
first[&"guest_definition_ids"][0] = "poisoned"
|
||||
var second := manifest(catalog)
|
||||
expect(same(expected, second), "first returned manifest never aliases live/private data")
|
||||
check_manifest_shape(expected, second, "second read")
|
||||
expect(catalog.factory_calls == (1 if cached else 2), "candidate fills once; reference rebuilds each read")
|
||||
second[&"species_ids"].clear()
|
||||
expect(same(expected, manifest(catalog)), "cache hit has independently owned nested arrays")
|
||||
|
||||
var entry := catalog.get_entry(&"guest_classes", &"cleric")
|
||||
entry[&"need_rates"][&"hunger"] = 99
|
||||
var ids := catalog.get_species_ids()
|
||||
ids.clear()
|
||||
var generated := catalog.build_guest_definition(&"cleric", &"human")
|
||||
generated.guest_type_id = &"poisoned_returned_definition"
|
||||
expect(same(expected, manifest(catalog)), "entry/ID/factory getters cannot mutate cached membership")
|
||||
|
||||
var prior_generation: int = int(catalog.call("get_manifest_generation")) if cached else -1
|
||||
var builds_before := catalog.factory_calls
|
||||
var docs := documents()
|
||||
docs[&"guest_classes"]["entries"][0]["base_patience"] = 61
|
||||
expect(catalog.rebuild_from_documents(docs).get(&"valid", false), "same IDs with changed real fields rebuild validly")
|
||||
if cached:
|
||||
expect(int(catalog.call("get_manifest_generation")) > prior_generation, "rebuild advances generation even when IDs do not change")
|
||||
expect(same(expected, manifest(catalog)) and catalog.factory_calls == builds_before+1, "changed definitions force original factory despite unchanged membership")
|
||||
docs[&"guest_classes"]["entries"][0]["unknown_definition_field"] = 1
|
||||
expect(not catalog.rebuild_from_documents(docs).get(&"valid", true), "unknown original fields remain fail-closed")
|
||||
var empty_expected := original_manifest(catalog)
|
||||
expect(same(empty_expected, manifest(catalog)), "invalid rebuild cannot expose earlier valid manifest")
|
||||
expect(catalog.rebuild().get(&"valid", false), "valid recovery uses normal file rebuild")
|
||||
expect(same(expected, manifest(catalog)), "valid recovery invalidates empty generation")
|
||||
expect(not catalog.rebuild({&"unknown_content_type":"res://missing.json"}).get(&"valid", true), "invalid path set is rejected")
|
||||
expect(same(original_manifest(catalog), manifest(catalog)), "failed path validation invalidates manifest")
|
||||
expect(catalog.rebuild().get(&"valid", false), "recover after path validation failure")
|
||||
var missing_paths: Dictionary = MvpContentCatalog.DEFAULT_PATHS.duplicate(true)
|
||||
missing_paths[&"guest_classes"] = "res://missing_manifest_cache_content.json"
|
||||
expect(not catalog.rebuild(missing_paths).get(&"valid", true), "read failure remains fail-closed")
|
||||
expect(same(original_manifest(catalog), manifest(catalog)), "read failure cannot retain prior successful generation")
|
||||
expect(catalog.rebuild().get(&"valid", false), "recover after read failure")
|
||||
|
||||
var replacement := CountingCatalog.new()
|
||||
expect(replacement.rebuild().get(&"valid", false), "new catalog identity is valid")
|
||||
runtime.mvp_content_catalog = replacement
|
||||
expect(same(expected, manifest(replacement)) and replacement.factory_calls == 1, "different identity cannot hit previous template")
|
||||
var same_generation := CountingCatalog.new()
|
||||
expect(same_generation.rebuild().get(&"valid", false), "second fresh identity has equivalent content")
|
||||
if cached:
|
||||
expect(replacement.call("get_manifest_generation") == same_generation.call("get_manifest_generation"), "identity test deliberately uses equal generations")
|
||||
runtime.mvp_content_catalog = same_generation
|
||||
expect(same(expected, manifest(same_generation)) and same_generation.factory_calls == 1, "equal generation on a new identity must still rebuild")
|
||||
replacement = same_generation
|
||||
# Temporary restore catalogs must be fully built without changing live cache.
|
||||
var temporary := CountingCatalog.new()
|
||||
temporary.rebuild()
|
||||
var live_calls := replacement.factory_calls
|
||||
manifest(temporary)
|
||||
manifest(temporary)
|
||||
expect(temporary.factory_calls == 2, "temporary catalog remains uncached for explicit restore validation")
|
||||
expect(same(expected, manifest(replacement)), "temporary validation preserves current content")
|
||||
if cached:
|
||||
expect(replacement.factory_calls == live_calls, "temporary validation does not replace live cache")
|
||||
|
||||
var old := expected.duplicate(true)
|
||||
old[&"manifest_version"] = 1
|
||||
old[&"staff_candidate_ids"].erase("staff_uma")
|
||||
var old_data := {&"catalog_manifest":old, &"hired_staff_candidate_ids":[], &"staff":{&"staff":[]}}
|
||||
if cached:
|
||||
runtime.call("_clear_mvp_content_manifest_cache")
|
||||
expect(runtime.call("_saved_content_manifest_matches", replacement, old_data), "sole prior chapel manifest remains accepted on first fill")
|
||||
expect(same(expected, manifest(replacement)), "legacy first-fill edits cannot poison current v2 template")
|
||||
expect(runtime.call("_saved_content_manifest_matches", replacement, old_data), "legacy prior manifest remains accepted on a hit")
|
||||
expect(same(expected, manifest(replacement)), "legacy hit edits leave staff_uma and manifest version intact")
|
||||
|
||||
var guard_before := complete_live_guard()
|
||||
var before: Dictionary = guard_before[&"snapshot"]
|
||||
var tampered := before.duplicate(true)
|
||||
for definition: Dictionary in tampered[&"systems"][&"runtime"][&"guests"][&"definitions"]:
|
||||
if String(definition[&"guest_type_id"]) == String(expected[&"guest_definition_ids"][0]):
|
||||
definition[&"wealth_min"] = int(definition[&"wealth_min"])+1
|
||||
break
|
||||
var catalog_before: MvpContentCatalog = runtime.mvp_content_catalog
|
||||
expect(not runtime.restore_snapshot(tampered), "cached manifest does not bypass full GuestDefinition value validation")
|
||||
expect(runtime.mvp_content_catalog == catalog_before and same(complete_live_guard(), guard_before), "rejected restore preserves identity and all full save-lifecycle guard fields")
|
||||
expect(runtime.restore_snapshot(before), "canonical save restores unchanged")
|
||||
expect(runtime.mvp_content_catalog != catalog_before and same(runtime.create_snapshot(), before), "successful restore replaces identity while preserving complete saved state")
|
||||
|
||||
var custom: GuestDefinition = runtime.guest_service.create_definition(&"manifest_late_guest", {&"adult_verified":true, &"wealth_min":10, &"wealth_max":20})
|
||||
expect(custom != null, "late custom registration remains supported")
|
||||
if custom != null:
|
||||
custom.base_patience = 42
|
||||
expect(runtime.guest_service.get_definition(&"manifest_late_guest").base_patience == 42, "live GuestService definition semantics remain intact")
|
||||
expect(same(expected, manifest(runtime.mvp_content_catalog)), "custom registered definitions do not replace catalog membership")
|
||||
var public_snapshot: Dictionary = runtime.create_snapshot()
|
||||
public_snapshot[&"systems"][&"runtime"][&"catalog_manifest"][&"guest_definition_ids"].clear()
|
||||
expect(same(expected, runtime.create_snapshot()[&"systems"][&"runtime"][&"catalog_manifest"]), "public full snapshot cannot mutate next snapshot manifest")
|
||||
runtime.call("_publish_snapshot", clock.absolute_minute)
|
||||
expect(same(runtime.latest_snapshot, runtime.create_snapshot()), "publication still eagerly contains all current components")
|
||||
var published_digest: String = runtime.latest_digest
|
||||
expect(published_digest == SimulationDigest.compute(runtime.latest_snapshot), "latest_digest remains exact")
|
||||
var replay_start: Dictionary = runtime.create_snapshot()
|
||||
clock.advance_minutes(7)
|
||||
var replay_end: Dictionary = runtime.create_snapshot()
|
||||
var retained_start := replay_start.duplicate(true)
|
||||
expect(runtime.restore_snapshot(replay_start), "retained checkpoint restores for tick replay")
|
||||
clock.advance_minutes(7)
|
||||
var repeated: Dictionary = runtime.create_snapshot()
|
||||
expect(same(replay_start, retained_start), "live ticks and restore never mutate retained public checkpoint")
|
||||
expect(same(replay_end, repeated), "full tick replay preserves every stored event/history/counter and component")
|
||||
expect(not SimulationDigest.canonical_bytes(repeated).is_empty() and SimulationDigest.canonical_bytes(repeated) == SimulationDigest.canonical_bytes(replay_end), "tick replay compares complete nonempty canonical bytes without event exclusions")
|
||||
if cached:
|
||||
var previous_identity: MvpContentCatalog = runtime.mvp_content_catalog
|
||||
expect(runtime.initialize(clock,state,840_102).get(&"valid", false), "repeat initialization remains valid")
|
||||
expect(runtime.mvp_content_catalog != previous_identity and same(expected, manifest(runtime.mvp_content_catalog)), "initialization clears old identity while preserving manifest values")
|
||||
runtime.free()
|
||||
clock.free()
|
||||
state.free()
|
||||
print("MANIFEST_CACHE_CORRECTNESS checks=%d failures=%d candidate=%s" % [checks, failures, cached])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cr07wcssbefah
|
||||
@@ -0,0 +1,24 @@
|
||||
extends SceneTree
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
func run() -> void:
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var path := PathService.new()
|
||||
var cells: Array[Vector3i] = []
|
||||
for x in 100:
|
||||
for y in 20: cells.append(Vector3i(x,y,0))
|
||||
path.rebuild_graph(cells)
|
||||
var cases: Array = [cells, [], cells.slice(1), cells.duplicate(), cells.duplicate()]
|
||||
cases[3][0] = cells[1]
|
||||
cases[4][0] = Vector3i(-1,0,0)
|
||||
var reverse := cells.duplicate();reverse.reverse();cases.append(reverse)
|
||||
var before := path.create_snapshot()
|
||||
var failures := 0
|
||||
for candidate: Array in cases:
|
||||
var typed: Array[Vector3i] = [];typed.assign(candidate)
|
||||
var reference: bool = runtime._path_snapshot_cells_match_world(path.create_snapshot(),typed)
|
||||
if path.matches_passable_cells(typed) != reference:failures+=1;push_error("membership differs from snapshot check")
|
||||
if path.create_snapshot()!=before:failures+=1;push_error("membership mutated path state")
|
||||
path.rebuild_graph([])
|
||||
if not path.matches_passable_cells([]):failures+=1;push_error("empty graphs must match")
|
||||
print("Path membership: 8 checks, %d failures" % failures)
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://co1jyiex61cfp
|
||||
@@ -0,0 +1,64 @@
|
||||
extends SceneTree
|
||||
const Demo := preload("res://scripts/ui/pixel_tower_demo.gd")
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok: failures += 1; push_error(label)
|
||||
func run() -> void:
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
var state: Variant = root.get_node("GameState")
|
||||
clock.reset(1,6,0); state.reset(); runtime.initialize(clock,state,424242); clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
|
||||
verify_detached(runtime, "new game")
|
||||
var demo := Demo.new()
|
||||
expect(await demo.prepare(runtime,clock,self), "populated production fixture prepares")
|
||||
verify_detached(runtime, "populated game")
|
||||
var retained: Dictionary = runtime.create_snapshot()
|
||||
var original := retained.duplicate(true)
|
||||
clock.advance_minutes(15)
|
||||
expect(retained == original, "advancing live actors never changes a retained snapshot")
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
expect(runtime.restore_snapshot(retained), "owned snapshot restores before deterministic replay")
|
||||
clock.advance_minutes(15)
|
||||
expect(runtime.create_snapshot() == after, "owned snapshot replays all subsystems identically")
|
||||
# Keep the general-purpose capture API's protective copy contract.
|
||||
var extra := {&"caller": {&"rows": [{&"value":1}]}}
|
||||
var generic: Dictionary = SimulationSnapshot.capture(clock,state,extra)
|
||||
extra[&"caller"][&"rows"][0][&"value"] = 2
|
||||
expect(generic[&"systems"][&"caller"][&"rows"][0][&"value"] == 1, "general capture still detaches caller-owned extras")
|
||||
print("Snapshot ownership: %d checks, %d failures" % [checks,failures]);quit(failures)
|
||||
|
||||
func verify_detached(runtime: Variant, stage: String) -> void:
|
||||
var expected: Dictionary = runtime.create_snapshot().duplicate(true)
|
||||
for section: Variant in expected[&"systems"][&"runtime"]:
|
||||
var candidate: Dictionary = runtime.create_snapshot()
|
||||
poison(candidate[&"systems"][&"runtime"][section])
|
||||
expect(runtime.create_snapshot() == expected, "%s detached section: %s" % [stage,section])
|
||||
if failures: return
|
||||
var candidate: Dictionary = runtime.create_snapshot()
|
||||
poison(candidate[&"clock"]);poison(candidate[&"game_state"])
|
||||
expect(runtime.create_snapshot() == expected, "%s clock and resource snapshots remain detached" % stage)
|
||||
|
||||
func poison(value: Variant) -> void:
|
||||
if value is Dictionary:
|
||||
for child: Variant in value.values(): poison(child)
|
||||
value.clear()
|
||||
value[&"ownership_probe"] = true
|
||||
elif value is Array:
|
||||
for child: Variant in value: poison(child)
|
||||
value.clear()
|
||||
match value.get_typed_builtin():
|
||||
TYPE_NIL: value.append(null)
|
||||
TYPE_DICTIONARY: value.append({})
|
||||
TYPE_ARRAY: value.append([])
|
||||
TYPE_STRING: value.append("ownership_probe")
|
||||
TYPE_STRING_NAME: value.append(&"ownership_probe")
|
||||
TYPE_INT: value.append(99)
|
||||
TYPE_FLOAT: value.append(99.0)
|
||||
TYPE_BOOL: value.append(true)
|
||||
TYPE_VECTOR2I: value.append(Vector2i(99,99))
|
||||
TYPE_VECTOR3I: value.append(Vector3i(99,99,99))
|
||||
_: expect(false, "ownership probe covers typed snapshot array %s" % value.get_typed_builtin())
|
||||
@@ -0,0 +1 @@
|
||||
uid://cq17j57epa3h5
|
||||
@@ -0,0 +1,84 @@
|
||||
extends SceneTree
|
||||
|
||||
const Linear := preload("res://tests/performance/fixtures/linear_path_reference.gd")
|
||||
const Uncached := preload("res://tests/performance/fixtures/uncached_preview_reference.gd")
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func expect(value: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
|
||||
func _run() -> void:
|
||||
var fast := PathService.new()
|
||||
var old := Linear.new()
|
||||
var cells: Array[Vector3i] = []
|
||||
for z in 2:
|
||||
for x in 30:
|
||||
for y in 20:
|
||||
cells.append(Vector3i(x, y, z))
|
||||
for service: PathService in [fast, old]:
|
||||
service.rebuild_graph(cells)
|
||||
service.configure_connector(&"door", Vector3i(15, 10, 0), Vector3i(15, 10, 1), 100, true, &"staff")
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 728191
|
||||
var fast_us := 0
|
||||
var old_us := 0
|
||||
for index in 120:
|
||||
var cell := cells[rng.randi_range(0, cells.size() - 1)]
|
||||
for service: PathService in [fast, old]:
|
||||
service.set_cell_dynamic_cost(cell, (index % 5) * 100)
|
||||
service.set_cell_temporary_block(cell, index % 7 == 0)
|
||||
service.set_connector_enabled(&"door", index % 9 != 0)
|
||||
var start := cells[rng.randi_range(0, cells.size() - 1)]
|
||||
var goal := cells[rng.randi_range(0, cells.size() - 1)]
|
||||
var access: Array[StringName] = []
|
||||
if index % 4 != 0:
|
||||
access.append(&"staff")
|
||||
var began := Time.get_ticks_usec()
|
||||
var expected := old.find_path(start, goal, null, access)
|
||||
old_us += Time.get_ticks_usec() - began
|
||||
began = Time.get_ticks_usec()
|
||||
var actual := fast.find_path(start, goal, null, access)
|
||||
fast_us += Time.get_ticks_usec() - began
|
||||
expect(actual == expected and fast.last_path_cost == old.last_path_cost and fast.last_no_route_reason == old.last_no_route_reason, "route, cost, reason must match linear reference: %d" % index)
|
||||
expect(fast.create_snapshot() == old.create_snapshot(), "query optimization must preserve snapshots")
|
||||
print("PATH_BENCH old_us=%d new_us=%d ratio=%.3f queries=120 cells=1200" % [old_us, fast_us, float(fast_us)/old_us])
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var preview := WorldPreview.new()
|
||||
var reference := Uncached.new()
|
||||
for view: WorldPreview in [preview, reference]:
|
||||
view.size = Vector2(1100, 600)
|
||||
view.set_runtime(runtime)
|
||||
var timings: Array[int] = []
|
||||
for view: WorldPreview in [reference, preview]:
|
||||
var began := Time.get_ticks_usec()
|
||||
for frame in 20:
|
||||
view._drawing = true
|
||||
view._draw_cache.clear()
|
||||
for i in 300:
|
||||
view.get_cell_rect(Vector3i(i % 20 - 10, i % 8, 0))
|
||||
view._drawing = false
|
||||
view._draw_cache.clear()
|
||||
timings.append(Time.get_ticks_usec() - began)
|
||||
for zoom: float in [0.5, 1.0, 2.0, 4.0]:
|
||||
for view: WorldPreview in [preview, reference]:
|
||||
view.zoom_level = zoom
|
||||
view.pan_offset = Vector2(19, -33)
|
||||
preview._drawing = true
|
||||
preview._draw_cache.clear()
|
||||
for i in 40:
|
||||
expect(preview.get_cell_rect(Vector3i(i - 20, i % 7, 0)) == reference.get_cell_rect(Vector3i(i - 20, i % 7, 0)), "cached camera mapping preserves cell geometry")
|
||||
preview._drawing = false
|
||||
preview._draw_cache.clear()
|
||||
expect(preview._draw_cache.is_empty(), "no derived cache survives a draw scope")
|
||||
print("GEOMETRY_BENCH old_us=%d new_us=%d ratio=%.3f cells=6000" % [timings[0], timings[1], float(timings[1])/timings[0]])
|
||||
preview.free()
|
||||
reference.free()
|
||||
print("PASS: %d checks; failures=%d" % [checks, failures])
|
||||
quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://duqbff3s0dowy
|
||||
@@ -0,0 +1,37 @@
|
||||
extends SceneTree
|
||||
var checks:=0
|
||||
var failures:=0
|
||||
func expect(ok: bool,label: String)->void:
|
||||
checks+=1
|
||||
if not ok:failures+=1;push_error(label)
|
||||
func _initialize()->void:call_deferred("run")
|
||||
func run()->void:
|
||||
var world:=WorldService.new()
|
||||
var bounds:={&"min_x":0,&"max_x":5,&"min_y":-2,&"max_y":2}
|
||||
world.reset(bounds)
|
||||
world.set_walkable(Vector3i(0,0,0),true)
|
||||
world.set_support(Vector3i(0,0,0),5,true)
|
||||
var original:=world.create_snapshot()
|
||||
var copy:=world.create_snapshot()
|
||||
copy[&"support"][0][&"capacity"]=99
|
||||
copy[&"bounds"][&"max_x"]=99
|
||||
copy[&"walkable_cells"].clear()
|
||||
expect(world.create_snapshot()==original,"cached snapshots remain caller-owned")
|
||||
world.consume_dirty_cells()
|
||||
expect(world.create_snapshot()[&"dirty_cells"].is_empty(),"dirty consumption is reflected without geometry revision")
|
||||
world.set_walkable(Vector3i(1,0,0),true)
|
||||
world.set_excavated(Vector3i(1,-1,0),true)
|
||||
expect(world.create_snapshot()[&"walkable_cells"].size()==2 and world.create_snapshot()[&"excavation_overrides"].size()==1,"geometry mutation invalidates cache")
|
||||
var replacement:=original.duplicate(true)
|
||||
replacement[&"walkable_cells"]=[Vector3i(2,0,0)]
|
||||
expect(world.restore_snapshot(replacement),"restore valid alternate world")
|
||||
expect(world.create_snapshot()==replacement,"restore invalidates cached geometry even with reused revision")
|
||||
replacement[&"walkable_cells"]=[Vector3i(3,0,0)]
|
||||
expect(world.restore_snapshot(replacement) and world.create_snapshot()==replacement,"same-revision alternate restore cannot reuse old geometry")
|
||||
var invalid:=replacement.duplicate(true);invalid[&"schema_version"]=-1
|
||||
expect(not world.restore_snapshot(invalid) and world.create_snapshot()==replacement,"failed restore preserves cached and authoritative state")
|
||||
world.reset(bounds)
|
||||
expect(world.create_snapshot()[&"support"].is_empty() and world.create_snapshot()[&"walkable_cells"].is_empty(),"reset invalidates same-revision geometry")
|
||||
world.expand_bounds({&"min_x":0,&"max_x":8,&"min_y":-2,&"max_y":2})
|
||||
expect(world.create_snapshot()[&"bounds"][&"max_x"]==8,"expansion updates cached bounds")
|
||||
print("World snapshot cache: %d checks, %d failures"%[checks,failures]);quit(failures)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bqfwliej8uua6
|
||||
Reference in New Issue
Block a user