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:
Takeshi
2026-09-30 23:52:24 +09:00
parent 6077e02ebe
commit 5dde0c5ec2
490 changed files with 54051 additions and 809 deletions
+32
View File
@@ -0,0 +1,32 @@
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)
class Fixture extends RefCounted:
var facility_service:=FacilityService.new()
var path_service:=PathService.new()
func _initialize()->void:call_deferred("run")
func run()->void:
var fixture:=Fixture.new()
var bridge=preload("res://scripts/environment/facility_cleaning_bridge.gd").new()
bridge.configure(fixture)
fixture.facility_service.create_instance({&"instance_id":&"target",&"definition_id":&"reception",&"origin":Vector3i(3,0,0),&"occupied_cells":[Vector3i(3,0,0),Vector3i(4,0,0)]})
var worker:=StaffRuntime.new();worker.current_cell=Vector3i(6,0,0)
var task:=StaffTask.new().configure(&"clean",&"facility_clean",50,0,{&"target_entity_id":&"target",&"target_cell":Vector3i(2,0,0),&"estimated_minutes":8})
var cells: Array[Vector3i]=[Vector3i(2,0,0),Vector3i(4,0,0),Vector3i(5,0,0),Vector3i(6,0,0)]
fixture.path_service.rebuild_graph(cells)
expect(bridge.prepare_route(task,worker) and task.target_cell==Vector3i(5,0,0),"isolated first access is replaced by reachable closest access")
var destination:=task.target_cell
expect(bridge.prepare_route(task,worker) and task.target_cell==destination,"reachable destination remains stable")
cells=[Vector3i(2,0,0),Vector3i(4,0,0),Vector3i(6,0,0)]
fixture.path_service.rebuild_graph(cells)
expect(destination in bridge.access_cells(fixture.facility_service.get_instance(&"target"),fixture.path_service,false) and destination not in bridge.access_cells(fixture.facility_service.get_instance(&"target"),fixture.path_service),"blocked saved destination remains geometrically valid while not walkable")
expect(not bridge.prepare_route(task,worker) and worker.current_cell==Vector3i(6,0,0) and task.target_cell==destination,"total isolation neither moves worker nor invents destination")
cells=[Vector3i(2,0,0),Vector3i(3,0,0),Vector3i(4,0,0),Vector3i(5,0,0),Vector3i(6,0,0)]
fixture.path_service.rebuild_graph(cells)
expect(bridge.prepare_route(task,worker),"route resumes after reconnection")
fixture.facility_service.unregister_instance(&"target")
expect(not bridge.prepare_route(task,worker),"removed facility has no work destination")
print("Cleaning access: %d checks, %d failures"%[checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://rvfm06u8dee3
@@ -0,0 +1,89 @@
extends SceneTree
const Cleanliness = preload("res://scripts/environment/facility_cleanliness_service.gd")
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 room: FacilityDefinition = load("res://data/facilities/expansion/cleaning_room.tres")
expect(room.is_valid() and room.footprint == Vector2i(2,1) and room.layer == FacilityDefinition.ViewLayer.BACK, "original cleaning room dimensions and layer")
expect(room.construction_cost == {&"gold":350,&"materials":4} and room.build_minutes == 60 and room.maintenance_gold == 2, "approved trial construction and upkeep")
expect(room.staff_min == 1 and room.staff_max == 2 and room.required_roles == [&"cleaner"], "approved cleaner staffing")
expect(room.customer_capacity == 0 and room.base_price == 0, "cleaning room has no customer use or fee")
var policy: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://data/environment/cleaning_policy.json"))
expect(policy["initial_cleanliness"] == 100 and policy["maximum_cleanliness"] == 100 and policy["clean_at_or_below"] == 75 and policy["work_minutes"] == 8 and policy["recovery_per_completed_task"] == 25, "approved trial cleaning policy")
var rules = preload("res://scripts/environment/facility_cleaning_policy.gd").new()
expect(rules.load_file(), "approved JSON policy loads through strict validator")
var approved: Dictionary = rules.create_snapshot()
var corrupt_policies: Array = []
for change: Dictionary in [{"schema_version":2}, {"initial_cleanliness":101}, {"maximum_cleanliness":0}, {"clean_at_or_below":100}, {"work_minutes":0}, {"work_minutes":8.5}, {"recovery_per_completed_task":0}, {"work_minutes":true}, {"work_minutes":INF}]:
var invalid_policy: Dictionary = approved.duplicate(true)
invalid_policy.merge(change, true)
corrupt_policies.append(invalid_policy)
var missing_policy: Dictionary = approved.duplicate(true)
missing_policy.erase("work_minutes")
corrupt_policies.append(missing_policy)
for invalid_policy: Dictionary in corrupt_policies:
expect(not rules.configure(invalid_policy) and rules.create_snapshot() == approved, "invalid policy rejects without replacing approved values")
var detached_policy: Dictionary = rules.create_snapshot()
detached_policy["work_minutes"] = 99
expect(rules.create_snapshot()["work_minutes"] == 8, "policy read cannot change work duration")
var ordered = Cleanliness.new()
var names: Array[String] = []
for index in range(31, -1, -1):
var name := "clean_order_%03d" % ((index * 17) % 32)
names.append(name)
ordered.register_facility(StringName(name), 75, 100)
names.sort()
var dirty := ordered.get_dirty_facility_ids(75)
var rows: Array = ordered.create_snapshot()["facilities"]
var text_order_matches := true
for index in names.size():
text_order_matches = text_order_matches and String(dirty[index]) == names[index] and rows[index]["id"] == names[index]
expect(text_order_matches, "generated IDs use lexical order for saves and cleaning dispatch")
var reordered: Dictionary = ordered.create_snapshot()
reordered["facilities"].reverse()
expect(ordered.restore_snapshot(reordered) and ordered.create_snapshot()["facilities"] == rows, "legacy row order restores to canonical lexical order")
var service = Cleanliness.new()
expect(service.register_facility(&"b", 100, 100) and service.register_facility(&"a", 50, 100), "explicit initial and maximum values")
expect(not service.register_facility(&"b", 100,100), "duplicate facility rejected")
expect(not service.register_facility(&"bad", -1,100), "invalid initial value rejected")
expect(service.record_use(&"b", &"visit",25), "completed visit soils facility")
expect(service.get_facility(&"b")["current"]==75, "soil amount applied")
expect(service.get_dirty_facility_ids(75)==[&"a",&"b"], "threshold inclusive with stable ordering")
expect(service.get_dirty_facility_ids(74)==[&"a"], "threshold excludes cleaner facility")
var saved: Dictionary=service.create_snapshot()
expect(service.record_use(&"b", &"visit",25) and service.create_snapshot()==saved, "replayed completion does not double soil")
expect(not service.record_use(&"b", &"visit",26) and service.create_snapshot()==saved, "conflicting completion is atomic")
expect(not service.record_use(&"missing", &"unknown",1), "unknown facility cannot receive effect")
expect(not service.record_cleaning(&"b",&"negative",-1), "negative recovery rejected")
expect(service.record_cleaning(&"b",&"work",9223372036854775807) and service.get_facility(&"b")["current"]==100, "recovery clamps without overflow")
expect(service.record_use(&"b",&"dirty",9223372036854775807) and service.get_facility(&"b")["current"]==0, "soil clamps without overflow")
expect(service.record_cleaning(&"b",&"work",9223372036854775807) and service.get_facility(&"b")["current"]==0, "old cleaning completion does not clean later dirt again")
var restored=Cleanliness.new()
expect(restored.restore_snapshot(service.create_snapshot()), "history and state restore")
expect(restored.create_snapshot()==service.create_snapshot(), "exact roundtrip")
expect(restored.record_cleaning(&"b",&"next",25) and service.record_cleaning(&"b",&"next",25) and restored.create_snapshot()==service.create_snapshot(), "restored continuation matches")
var detached: Dictionary=restored.get_facility(&"b");detached["current"]=99
expect(restored.get_facility(&"b")["current"]==25, "read result detached")
var intact: Dictionary=restored.create_snapshot()
var bad_cases: Array=[]
var bad: Dictionary=intact.duplicate(true);bad["schema_version"]=2;bad_cases.append(bad)
bad=intact.duplicate(true);bad["facilities"][0]["current"]=33;bad_cases.append(bad)
bad=intact.duplicate(true);bad["facilities"].append(bad["facilities"][0].duplicate(true));bad_cases.append(bad)
bad=intact.duplicate(true);bad["receipts"].append(bad["receipts"][0].duplicate(true));bad_cases.append(bad)
bad=intact.duplicate(true);bad["receipts"][0]["facility_id"]="missing";bad_cases.append(bad)
bad=intact.duplicate(true);bad["receipts"][0]["amount"]=1.5;bad_cases.append(bad)
bad=intact.duplicate(true);bad["receipts"][0]["kind"]="unknown";bad_cases.append(bad)
bad=intact.duplicate(true);bad["facilities"]=false;bad_cases.append(bad)
bad=intact.duplicate(true);bad["unexpected"]=1;bad_cases.append(bad)
for candidate: Dictionary in bad_cases:
expect(not restored.restore_snapshot(candidate) and restored.create_snapshot()==intact, "malformed or inconsistent save preserves live state")
var mutated: Dictionary=restored.create_snapshot();mutated["receipts"].clear();mutated["facilities"].clear()
expect(restored.create_snapshot()==intact, "saved arrays do not alias live state")
expect(restored.unregister_facility(&"b") and restored.get_facility(&"b").is_empty(), "demolition removes facility state")
var after_remove=Cleanliness.new()
expect(after_remove.restore_snapshot(restored.create_snapshot()), "demolition retains no dangling completion receipts")
print("Facility cleanliness: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://b0g1ir0itwtlx
@@ -0,0 +1,74 @@
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 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()
expect(runtime.initialize(clock,state,42630)[&"valid"], "initialize with validated approved policy")
clock.set_paused(true)
expect(runtime.submit_command(&"build_facility", {&"definition_id":&"reception",&"origin":Vector3i(-6,0,0)}).is_success(), "normal paid construction begins")
expect(runtime.facility_cleanliness.create_snapshot()["facilities"].is_empty(), "unfinished building has no cleanliness record")
clock.advance_minutes(120)
var facility: FacilityInstance = runtime.facility_service.get_instances()[0]
var id := facility.instance_id
expect(runtime.facility_cleanliness.get_facility(id)["current"] == 100, "construction completion starts clean")
expect(runtime.submit_command(&"set_facility_conditions",{&"facility_id":id,&"conditions":{&"active_staff_ids":[&"reception_fixture"]}}).is_success(), "controlled staffing for transaction test")
expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":&"payer",&"gold":100}).is_success(), "payer wallet created")
expect(runtime.submit_command(&"create_service_request",{&"request_id":&"visit1",&"facility_id":id,&"guest_ids":[&"payer"],&"service_type":&"check_in"}).is_success(), "normal service request")
clock.advance_one_minute()
var active: Array = runtime.facility_service.get_active_services(id)
expect(active.size()==1, "service actually starts")
if active.is_empty(): runtime.free(); clock.free(); state.free(); quit(failures); return
var service_id: StringName = active[0].service_id
expect(runtime.facility_cleanliness.get_facility(id)["current"]==100, "starting a visit does not apply completion dirt")
var midpoint: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(midpoint), "in-progress service with cleanliness restores")
clock.advance_minutes(21)
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED and runtime.facility_cleanliness.get_facility(id)["current"]==99, "completed reception visit applies defined dirt once")
var complete: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(complete) and runtime.create_snapshot()==complete, "completed dirt and visit roundtrip exactly")
clock.advance_one_minute()
expect(runtime.facility_cleanliness.get_facility(id)["current"]==99, "saved completion does not repeat dirt")
expect(runtime.restore_snapshot(midpoint), "rewind visit")
clock.advance_minutes(21)
expect(runtime.create_snapshot()==complete, "save continuation reproduces full Runtime exactly")
var intact: Dictionary=runtime.create_snapshot()
var bad: Dictionary=intact.duplicate(true)
bad[&"systems"][&"runtime"].erase(&"facility_cleanliness")
expect(not runtime.restore_snapshot(bad) and runtime.create_snapshot()==intact, "v12 missing cleanliness rejects atomically")
bad=intact.duplicate(true)
bad[&"systems"][&"runtime"][&"facility_cleanliness"]["receipts"][0]["source_id"]="invented"
expect(not runtime.restore_snapshot(bad) and runtime.create_snapshot()==intact, "receipt without completed visit rejects atomically")
bad=intact.duplicate(true)
bad[&"systems"][&"runtime"][&"facility_cleanliness"]["facilities"]=[]
bad[&"systems"][&"runtime"][&"facility_cleanliness"]["receipts"]=[]
expect(not runtime.restore_snapshot(bad) and runtime.create_snapshot()==intact, "missing live facility cleanliness rejects")
var legacy: Dictionary=intact.duplicate(true)
legacy[&"systems"][&"runtime"][&"runtime_schema_version"]=11
legacy[&"systems"][&"runtime"].erase(&"facility_cleanliness")
expect(runtime.restore_snapshot(legacy) and runtime.facility_cleanliness.get_facility(id)["current"]==100, "v11 initializes cleanliness without guessing past dirt")
expect(runtime.create_snapshot()[&"systems"][&"runtime"][&"weather"]==intact[&"systems"][&"runtime"][&"weather"], "v11 weather survives migration unchanged")
expect(runtime.restore_snapshot(intact), "restore current dirt before rollback checks")
var rollback: Dictionary=runtime.call("_capture_infrastructure_demolition_state")
runtime.facility_cleanliness.unregister_facility(id)
expect(runtime.call("_restore_infrastructure_demolition_state",rollback) and runtime.create_snapshot()==intact, "infrastructure rollback includes cleanliness")
# Force the final dirt boundary to fail after payment/output staging; no partial settlement.
expect(runtime.restore_snapshot(midpoint), "rewind for failed cleanliness commit")
runtime.facility_cleanliness.unregister_facility(id)
clock.advance_minutes(21)
expect(runtime.facility_service.get_service(service_id).state!=ServiceInstance.State.COMPLETED, "missing dirt participant prevents partial service completion")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:payer")==100, "failed cleanliness transaction leaves wallet unchanged")
runtime.facility_cleanliness.register_facility(id,100,100)
clock.advance_one_minute()
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED and runtime.facility_cleanliness.get_facility(id)["current"]==99, "failed boundary retries once after recovery")
expect(runtime.submit_command(&"demolish_facility",{&"instance_id":id}).is_success(), "normal demolition command")
expect(runtime.facility_cleanliness.get_facility(id).is_empty() and runtime.facility_cleanliness.create_snapshot()["receipts"].is_empty(), "demolition removes state and dangling receipts")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "post-demolition Runtime remains restorable")
runtime.free();clock.free();state.free()
print("Runtime cleanliness: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://didx6256rlc4d
+51
View File
@@ -0,0 +1,51 @@
extends SceneTree
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func run() -> void:
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()
expect(runtime.initialize(clock,state,424242)[&"valid"], "initialize Runtime")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable production arrivals")
var original: Dictionary = runtime.create_snapshot()
var legacy := original.duplicate(true)
legacy[&"systems"][&"runtime"][&"runtime_schema_version"] = 10
legacy[&"systems"][&"runtime"].erase(&"weather")
legacy[&"systems"][&"runtime"].erase(&"facility_cleanliness")
expect(runtime.restore_snapshot(legacy) and runtime.weather.get_kind() == &"clear" and runtime.weather.get_minute() == 360, "v10 migrates to neutral weather at saved minute")
expect(runtime.create_snapshot() == original, "old neutral save migration preserves other state")
var malformed := original.duplicate(true)
malformed[&"systems"][&"runtime"][&"weather"][&"minute"] = 361
expect(not runtime.restore_snapshot(malformed) and runtime.create_snapshot() == original, "future weather clock rejects atomically")
malformed = original.duplicate(true)
malformed[&"systems"][&"runtime"].erase(&"weather")
expect(not runtime.restore_snapshot(malformed), "new schema cannot silently lose weather")
expect(runtime.weather.schedule(&"storm",390)[&"ok"] and runtime.weather.schedule(&"clear",420)[&"ok"], "schedule weather boundaries")
clock.advance_minutes(29)
var before: Dictionary = runtime.create_snapshot()
expect(runtime.weather.get_kind() == &"clear", "storm does not apply before boundary")
var spawned := {&"count":0}
runtime.guest_service.guest_spawned.connect(func(_guest: GuestRuntime) -> void: spawned[&"count"] += 1)
clock.advance_minutes(1)
expect(runtime.weather.get_kind() == &"storm" and spawned[&"count"] == 0, "storm starts before same-minute production admission")
var boundary: Dictionary = runtime.call("_operations_arrival_boundary", {}, OperationsCoordinator.PHASE_CAPTURE, null)
expect(boundary[&"capacity_remaining"] == 0, "storm exposes zero admission capacity")
var stopped: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(stopped) and runtime.create_snapshot() == stopped, "storm and recovery schedule round trip exactly")
clock.advance_minutes(30)
expect(runtime.weather.get_kind() == &"clear", "scheduled recovery resumes weather at boundary")
boundary = runtime.call("_operations_arrival_boundary", {}, OperationsCoordinator.PHASE_CAPTURE, null)
expect(boundary[&"capacity_remaining"] > 0, "recovery restores admission capacity")
var recovered: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(before), "rewind before storm")
clock.advance_minutes(31)
expect(runtime.create_snapshot() == recovered, "full Runtime weather and arrivals replay identically")
expect(not runtime.get_operations_report()[&"runtime_failure_latched"], "weather does not quarantine operations")
runtime.free(); clock.free(); state.free()
print("Runtime weather: %d checks, %d failures" % [checks, failures]); quit(failures)
@@ -0,0 +1 @@
uid://c524n3edolklf
@@ -0,0 +1,39 @@
extends SceneTree
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func run() -> void:
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()
expect(runtime.initialize(clock,state,424242)[&"valid"], "initialize")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable arrivals")
expect(runtime.weather.schedule(&"storm",390)[&"ok"] and runtime.weather.schedule(&"clear",420)[&"ok"], "schedule storm and recovery")
clock.advance_minutes(29)
runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", func(_payload: Dictionary, _phase: StringName, _token: Variant) -> Dictionary: return {&"ok": false})
clock.advance_minutes(1)
expect(clock.absolute_minute == 390 and runtime.get_operations_report()[&"runtime_failure_latched"], "arrival failure pauses at weather boundary")
expect(runtime.weather.get_kind() == &"storm" and runtime.weather.get_minute() == 390, "weather commits before failed arrival")
var weather_before: Dictionary = runtime.weather.create_snapshot()
var path := "user://tests/weather_recovery_%d" % OS.get_process_id()
expect(runtime.save_to_path(path)[&"ok"], "write interrupted weather to actual save package")
expect(runtime.load_from_path(path)[&"ok"] and runtime.weather.create_snapshot() == weather_before, "disk load preserves storm and pending recovery")
expect(runtime.get_operations_report()[&"deferred_runtime_minute"] == 390, "disk load preserves failed boundary for retry")
runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", Callable())
var arrivals := {&"count":0}
runtime.guest_service.guest_spawned.connect(func(_guest: GuestRuntime) -> void: arrivals[&"count"] += 1)
expect(runtime.acknowledge_operations_pause()[&"ok"], "retry failed storm boundary")
expect(arrivals[&"count"] == 0 and runtime.weather.create_snapshot() == weather_before, "retry neither admits storm guests nor duplicates weather transition")
var completed: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(completed) and runtime.create_snapshot() == completed, "completed retry restores exactly")
clock.advance_minutes(30)
expect(runtime.weather.get_kind() == &"clear" and runtime.weather.create_snapshot()[&"pending"].is_empty(), "disk-restored recovery occurs once at scheduled minute")
expect(not runtime.get_operations_report()[&"runtime_failure_latched"], "recovery resumes ordinary operations")
expect(SaveService.delete_save(path), "delete isolated test save")
runtime.free(); clock.free(); state.free()
print("Weather recovery: %d checks, %d failures" % [checks, failures]); quit(failures)
@@ -0,0 +1 @@
uid://dgtxyr8gxg6nj
@@ -0,0 +1,43 @@
extends SceneTree
const Weather = preload("res://scripts/environment/weather_timeline.gd")
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void:
var weather = Weather.new()
expect(not weather.permits_new_arrivals(), "uninitialized weather never grants admission")
expect(not weather.initialize(&"unknown", 360) and not weather.initialize(&"clear", 1.5), "invalid initial data rejected")
expect(weather.initialize(&"clear", 360), "caller explicitly chooses initial weather and time")
expect(not weather.initialize(&"storm", 400), "initialize cannot reset a live simulation")
expect(weather.schedule(&"clear", 390)[&"ok"] and weather.schedule(&"storm", 380)[&"ok"] and weather.schedule(&"rain", 370)[&"ok"], "out of order scheduling retains chronological execution")
expect(weather.schedule(&"storm", 380)[&"replayed"] and not weather.schedule(&"rain", 380)[&"ok"], "same boundary retry is idempotent and conflict rejected")
expect(not weather.schedule(&"storm", 360)[&"ok"], "past and current weather cannot be retroactively changed")
var checkpoint: Dictionary = weather.create_snapshot()
expect(weather.advance_to(379)[&"changes"] == [{&"from": &"clear", &"to": &"rain", &"minute": 370}], "rain changes at its scheduled time")
expect(weather.permits_new_arrivals(), "rain is not treated as a storm admission ban")
expect(weather.advance_to(380)[&"changes"].size() == 1 and not weather.permits_new_arrivals(), "storm blocks admission at exact boundary")
var storm: Dictionary = weather.create_snapshot()
var replay: Dictionary = weather.advance_to(380)
expect(replay[&"replayed"] and replay[&"changes"].is_empty() and weather.create_snapshot() == storm, "same minute cannot duplicate a transition")
expect(not weather.advance_to(379)[&"ok"] and weather.create_snapshot() == storm, "time reversal cannot change state")
expect(weather.advance_to(390)[&"ok"] and weather.permits_new_arrivals(), "clear restores admission")
var finished: Dictionary = weather.create_snapshot()
expect(weather.restore_snapshot(checkpoint), "restore earlier weather and pending schedule")
expect(weather.advance_to(390)[&"changes"].size() == 3 and weather.create_snapshot() == finished, "jump and minute-step replay produce identical weather state")
var encoded: Dictionary = bytes_to_var(var_to_bytes(storm))
expect(weather.restore_snapshot(encoded) and weather.get_kind() == &"storm", "production Variant codec preserves integer weather time and IDs")
var baseline: Dictionary = weather.create_snapshot()
for field: String in ["version", "minute", "kind", "since", "pending"]:
var bad := baseline.duplicate(true); bad.erase(field)
expect(not weather.restore_snapshot(bad) and weather.create_snapshot() == baseline, "missing field rejects atomically: " + field)
for change: Dictionary in [{"version": 2}, {"minute": 380.0}, {"kind": "snow"}, {"since": 381}, {"pending": [{"kind":"clear","minute":380}]}, {"pending": [{"kind":"clear","minute":400},{"kind":"rain","minute":390}]}, {"extra":true}]:
var bad := baseline.duplicate(true)
bad.merge(change, true)
expect(not weather.restore_snapshot(bad) and weather.create_snapshot() == baseline, "malformed or contradictory state rejects atomically")
var detached: Dictionary = weather.create_snapshot(); detached[&"pending"].clear()
expect(not weather.create_snapshot()[&"pending"].is_empty(), "caller cannot mutate pending weather through snapshot alias")
expect(weather.schedule(&"mana_fog", 400)[&"ok"] and weather.advance_to(400)[&"ok"] and weather.get_kind() == &"mana_fog", "mana fog is persisted without invented efficiency multipliers")
print("Weather timeline: %d checks, %d failures" % [checks, failures])
quit(failures)
@@ -0,0 +1 @@
uid://cmqkqajl1u1d
+50 -2
View File
@@ -17,14 +17,14 @@ func _run() -> void:
_expect(not FileAccess.file_exists("res://project.godot") and not FileAccess.file_exists("res://data/facilities/reception.tres"), "release regression has no source checkout fallback")
print("RECEPTION_RESOURCE file=%s resource=%s" % [FileAccess.file_exists("res://data/facilities/reception.tres"), ResourceLoader.exists("res://data/facilities/reception.tres")])
_expect(runtime.definition_registry.is_valid(), "exported facility definitions validate: %s" % runtime.definition_registry.get_report())
_expect(runtime.definition_registry.facility_count() == 18, "all 18 buildable definitions are present in the shipped pack")
_expect(runtime.definition_registry.facility_count() == 23, "all 23 buildable definitions are present in the shipped pack")
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
var buttons: Dictionary = main.get("build_buttons")
_expect(buttons.size() == 18, "the actual build palette exposes all 18 facilities")
_expect(buttons.size() == 23, "the actual build palette exposes all 23 facilities")
_expect(buttons.has(&"reception"), "reception is selectable in the actual build palette")
if not buttons.has(&"reception"):
_finish()
@@ -49,6 +49,15 @@ func _run() -> void:
reception = facility
_expect(reception != null, "the reception completes from the exported game data")
_expect(guide != null and guide.get_progress().get(&"stage") == &"room", "opening guide advances to building a room")
(main.get("guest_count_label") as Button).pressed.emit()
await process_frame
var observer: Variant = main.get("guest_observer")
_expect(observer != null and observer.visible, "shipped HUD opens guest observation")
var guest_panel: Variant = observer.find_child("GuestObservationPanel", true, false) if observer != null else null
_expect(guest_panel != null and guest_panel.bars.size() == 4, "shipped observation includes all four need bars")
if observer != null:
observer.hide()
await _check_original_facilities(main, runtime, clock)
if OS.get_environment("THE_TREE_EXPORT_CAPTURE") == "1":
main.call("_refresh_dashboard")
await process_frame
@@ -65,3 +74,42 @@ func _expect(ok: bool, label: String) -> void:
func _finish() -> void:
print("Exported opening: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
func _check_original_facilities(main: Control, runtime: Variant, clock: Variant) -> void:
var buttons: Dictionary = main.get("build_buttons")
for entry: Dictionary in [
{&"id": &"premium_room", &"origin": Vector3i(-8,2,0), &"size":Vector2i(4,1), &"upkeep":5},
{&"id": &"warehouse", &"origin": Vector3i(-8,0,1), &"size":Vector2i(4,2), &"upkeep":3},
{&"id": &"staff_dormitory", &"origin": Vector3i(-8,2,1), &"size":Vector2i(2,1), &"upkeep":4},
]:
var definition: Variant = runtime.definition_registry.get_facility(entry[&"id"])
_expect(definition != null and definition.footprint == entry[&"size"] and definition.maintenance_gold == entry[&"upkeep"], "packed original geometry and upkeep: %s" % entry[&"id"])
if definition == null or not buttons.has(entry[&"id"]): return
buttons[entry[&"id"]].button_pressed = false
buttons[entry[&"id"]].button_pressed = true
_expect(main.get("_selected_definition_id") == entry[&"id"] and main.get("world_preview").layer_mode == entry[&"origin"].z, "packed palette selects correct facility/layer: %s" % entry[&"id"])
main.call("_on_map_cell_clicked", entry[&"origin"])
clock.advance_minutes(definition.build_minutes)
var found := false
for facility: Variant in runtime.facility_service.get_instances():
if facility.definition_id == entry[&"id"]:
found = facility.occupied_cells.size() == entry[&"size"].x * entry[&"size"].y
_expect(found, "packed construction completes correct occupied cells: %s" % entry[&"id"])
var path := "user://tests/export_original_%d" % Time.get_ticks_usec()
var snapshot: Dictionary = runtime.create_snapshot()
_expect(runtime.save_to_path(path)[&"ok"], "packed runtime writes completed original facilities")
clock.advance_minutes(3)
var loaded: Dictionary = runtime.load_from_path(path)
var restored: Dictionary = runtime.create_snapshot()
var expected_runtime: Dictionary = snapshot[&"systems"][&"runtime"]
var restored_runtime: Dictionary = restored[&"systems"][&"runtime"]
var events: Dictionary = restored_runtime[&"domain_events"]
var last: Dictionary = events["recent_events"].back()
_expect(loaded[&"ok"] and last["event_type"] == &"SAVE_LOADED" and events["published_ids"].size() == expected_runtime[&"domain_events"]["published_ids"].size()+1, "load publishes exactly one successful lifecycle event")
# Loading intentionally appends SAVE_LOADED; all other saved gameplay state
# must match, including every ID counter except this one new event.
expected_runtime[&"domain_events"] = events
expected_runtime[&"id_generator"]["counters"]["domain_event"] += 1
_expect(restored == snapshot, "packed runtime restores complete gameplay state with only the load event added")
SaveService.delete_save(path)
_expect(main.find_child("SaveLoadMenu", true, false) != null, "packed UI includes migrated-save load menu")
+24
View File
@@ -0,0 +1,24 @@
[gd_resource type="Resource" script_class="FacilityDefinition" load_steps=2 format=3]
[ext_resource type="Script" path="res://scripts/data/facility_definition.gd" id="1"]
[resource]
script = ExtResource("1")
id = &"premium_room"
display_name = "上級客室"
description = "富裕客と長期滞在客向けの静かな地上客室。一般客室より高い品質と料金を持つ。"
zone = 1
layer = 0
footprint = Vector2i(3, 2)
construction_cost = {&"gold": 900, &"materials": 10}
build_minutes = 150
weight_per_cell = 2
maintenance_mana = 1
staff_min = 0
staff_max = 0
capacity = 2
queue_capacity = 2
base_service_minutes = 720
base_quality = 70
base_price = 220
tags = Array[StringName]([&"expansion", &"lodging", &"premium", &"quiet"])
+25
View File
@@ -0,0 +1,25 @@
[gd_resource type="Resource" script_class="FacilityDefinition" load_steps=2 format=3]
[ext_resource type="Script" path="res://scripts/data/facility_definition.gd" id="1"]
[resource]
script = ExtResource("1")
id = &"staff_dormitory"
display_name = "従業員宿舎"
description = "勤務の合間に従業員が睡眠と長時間休息を取るBACK居住区画。"
zone = 1
layer = 1
footprint = Vector2i(4, 2)
construction_cost = {&"gold": 650, &"materials": 8}
build_minutes = 150
weight_per_cell = 1
maintenance_mana = 1
power_priority = 35
staff_min = 0
staff_max = 0
capacity = 12
queue_capacity = 0
base_service_minutes = 360
base_quality = 58
base_price = 0
tags = Array[StringName]([&"back_of_house", &"expansion", &"recovery", &"staff_only", &"staff_support"])
+24
View File
@@ -0,0 +1,24 @@
extends RefCounted
## Build a genuine pre-v10 fixture only when no upkeep ledger history exists.
static func strip_empty_upkeep(data: Dictionary) -> void:
for entry: Dictionary in data[&"economy"][&"entries"]:
assert(String(entry.get(&"category", "")) not in ["MAINTENANCE_ACCRUAL", "MAINTENANCE_PAYMENT"])
data.erase(&"weather")
data.erase(&"facility_cleanliness")
data.erase(&"facility_upkeep")
data.erase(&"upkeep_settlement")
var accounts: Array = data[&"economy"][&"accounts"]
for index in range(accounts.size() - 1, -1, -1):
if String(accounts[index][&"account_id"]) in ["MAINTENANCE_EXPENSE", "MAINTENANCE_PAYABLE"]:
accounts.remove_at(index)
static func migrated_accrual(minute: int) -> Dictionary:
var accrual := FacilityUpkeepAccrual.new()
accrual.start_at(minute)
return accrual.create_snapshot()
static func migrated_weather(minute: int) -> Dictionary:
var weather = preload("res://scripts/environment/weather_timeline.gd").new()
weather.initialize(&"clear", minute)
return weather.create_snapshot()
+1
View File
@@ -0,0 +1 @@
uid://b0s2m5gt63axs
+11
View File
@@ -0,0 +1,11 @@
# Operations legacy fixture
`operations_v3_0600.txt` is a Godot `var_to_str` snapshot generated using the unchanged
`OperationsCoordinator` at commit `6077e02`, before the midnight migration.
Only its class name was renamed to load it alongside the current coordinator.
Start minute: 0. Advance target: 3300. Daily callback: revenue 100, wages 10,
arrears 10, no facilities/anomalies, reconciliation difference 0.
The two positive-profit summaries close at 1800 and 3240; the legacy arrears
counter is 2. These deliberately distinguish historical arrears from new losses.
The fixture is read as data with `str_to_var`; it never touches player saves.
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
extends "res://tests/ui/test_chapel_gameplay.gd"
## Original effect fixture only: recruitment values remain unconfigured.
func cleric_id() -> StringName: return &"heroine_amelia"
func donation_price() -> int: return 60
func recruit_cleric(_main: Control, runtime: Variant) -> void:
expect(runtime.submit_command(&"register_staff", {&"staff_id":cleric_id(), &"role_id":&"cleric", &"options":{&"employment_kind":&"UNIQUE", &"current_cell":Vector3i(-8,0,0)}}).is_success(), "register unique Amelia test fixture")
func verify_effect(runtime: Variant, chapel: FacilityInstance) -> void:
expect(runtime.get_facility_business_price(chapel)==60, "assigned Amelia triples chapel donation")
var saved: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(saved), "placement passive restores")
chapel=runtime.facility_service.get_instance(chapel.instance_id)
expect(runtime.get_facility_business_price(chapel)==60, "restoring never stacks donation multiplier")
var worker: StaffRuntime=runtime.staff_service.get_staff(cleric_id())
worker.employment_kind=StaffRuntime.EmploymentKind.FREE_HIRE
expect(runtime.get_facility_business_price(chapel)==20, "same ID with ordinary employment has no unique effect")
worker.employment_kind=StaffRuntime.EmploymentKind.UNIQUE
worker.role_id=&"porter"
expect(runtime.get_facility_business_price(chapel)==20, "unrelated role has no donation effect")
worker.role_id=&"cleric"
var other:=FacilityInstance.new()
other.instance_id=&"other_chapel";other.definition_id=&"chapel"
expect(runtime.get_donation_multiplier(other)==1, "other chapel does not inherit placement effect")
other.instance_id=chapel.instance_id;other.definition_id=&"standard_room"
expect(runtime.get_donation_multiplier(other)==1, "lodging is not a donation")
expect(runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"heroine_anri"}).is_success(), "normal Anri recruitment for combined effect")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri", &"facility_id":chapel.instance_id, &"template_id":&"DAY"}).is_success(), "assign manager alongside chapel cleric")
expect(runtime.get_facility_business_price(chapel)==72, "triple donation combines once with approved floor20percent")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri", &"facility_id":&"", &"template_id":&"DAY"}).is_success(), "remove manager without changing cleric")
expect(runtime.get_facility_business_price(chapel)==60, "removing manager keeps exactly one donation passive")
var business: BusinessManagementPanel=root.get_node("Main").find_child("BusinessManagementPanel",true,false)
business.select_facility(chapel.instance_id)
expect(business.quote_label.text.contains("60G") and business.effect_label.text.contains("寄付金3倍"), "normal business UI explains actual donation")
func verify_after_start(runtime: Variant, chapel: FacilityInstance) -> void:
var guest: GuestRuntime=runtime.guest_service.get_guest(&"worship_walker")
var service: ServiceInstance=runtime.facility_service.get_service(guest.current_service_id)
expect(service.price_snapshot==60, "autonomous visitor freezes triple donation before worship")
expect(runtime.submit_command(&"set_facility_policy", {&"facility_id":chapel.instance_id, &"patch":{&"price_mode":&"expensive"}}).is_success(), "change donation policy during worship")
expect(runtime.get_facility_business_price(runtime.facility_service.get_instance(chapel.instance_id))==90 and service.price_snapshot==60, "new90G quote never rewrites accepted60G donation")
var saved: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(saved), "save active fixed quote with changed current policy")
expect(runtime.facility_service.get_service(service.service_id).price_snapshot==60, "restore preserves original donation")
expect(runtime.submit_command(&"set_facility_policy", {&"facility_id":chapel.instance_id, &"patch":{&"price_mode":&"standard"}}).is_success(), "return policy through ordinary command")
func verify_completion(runtime: Variant, chapel: FacilityInstance, service_id: StringName) -> void:
var sales := 0
var donor_paid := 0
for entry: LedgerEntry in runtime.economy_service.get_entries():
if entry.source_entity_id==chapel.instance_id and entry.category==&"SERVICE_SALE":
sales+=entry.amount
if entry.credit_account==&"GUEST_WALLET:worship_walker": donor_paid+=entry.amount
expect(donor_paid==60 and sales>=donor_paid and runtime.economy_service.validate_ledger()[&"ok"], "visitor60G payment is recorded exactly once as chapel revenue")
expect(runtime.facility_service.get_service(service_id).price_snapshot==60, "completion retains original quote")
+1
View File
@@ -0,0 +1 @@
uid://b1fq74rwro825
+118
View File
@@ -0,0 +1,118 @@
extends SceneTree
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func run() -> void:
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(); runtime.initialize(clock,state,42602); clock.set_paused(true)
for row: Array in [[&"reception",Vector3i(-6,0,0)],[&"warehouse",Vector3i(-6,0,1)],[&"premium_room",Vector3i(-6,2,0)],[&"warehouse",Vector3i(-6,2,1)],[&"service_stairs",Vector3i(-7,1,1)],[&"service_stairs",Vector3i(-7,2,1)]]:
var built: CommandResult = runtime.submit_command(&"build_facility",{&"definition_id":row[0],&"origin":row[1]})
expect(built.is_success(), "build floor-effect fixture %s: %s" % [row[0],built.message])
if not built.is_success(): runtime.free();clock.free();state.free();quit(failures);return
clock.advance_minutes(runtime.definition_registry.get_facility(row[0]).build_minutes)
var room: FacilityInstance
var workplace: FacilityInstance
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"premium_room": room=facility
if facility.definition_id == &"warehouse" and facility.origin.y==2: workplace=facility
expect(runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"heroine_anri"}).is_success(), "recruit canonical free Anri")
var assigned: CommandResult=runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":workplace.instance_id,&"template_id":&"DAY"})
expect(assigned.is_success(), "manager placement uses real capacity and accessible path: %s" % assigned.message)
expect(runtime.get_floor_price_multiplier(2)==12000 and runtime.get_floor_price_multiplier(0)==10000, "placement affects only assigned floor across layers")
expect(runtime.get_facility_business_price(room)==264, "premium room on opposite layer quotes 220 plus20 percent")
clock.advance_minutes(20)
expect(workplace.active_staff_ids.is_empty(), "manager placement does not impersonate required warehouse porters")
var rows: Array=runtime.get_business_management_report()[&"facilities"]
for row: Dictionary in rows:
if row[&"facility_id"]==room.instance_id: expect(row[&"price"]==264 and row[&"floor_price_multiplier_bp"]==12000, "business UI report matches charged price")
expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":&"anri_payer",&"gold":1000}).is_success(), "fund controlled service wallet")
expect(runtime.submit_command(&"create_service_request",{&"request_id":&"anri_stay",&"facility_id":room.instance_id,&"guest_ids":[&"anri_payer"],&"service_type":&"lodging"}).is_success(), "normal request captures floor-adjusted price")
clock.advance_one_minute()
var active: Array=runtime.facility_service.get_active_services(room.instance_id)
expect(active.size()==1 and active[0].price_snapshot==264, "started stay freezes approved guest price")
if active.is_empty(): runtime.free();clock.free();state.free();quit(failures);return
var service_id: StringName=active[0].service_id
var saved: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(saved), "save restores unique placement and frozen service together")
expect(runtime.get_floor_price_multiplier(2)==12000, "restored placement derives multiplier without stacking")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":&"",&"template_id":&"DAY"}).is_success(), "unassign manager while guest stay continues")
expect(runtime.get_floor_price_multiplier(2)==10000 and runtime.get_facility_business_price(runtime.facility_service.get_instance(room.instance_id))==220, "new prices revert after unassignment")
clock.advance_minutes(721)
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED, "frozen-price service completes normally")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:anri_payer")==736, "customer pays exactly264 once despite later unassignment")
var revenue:=0
for entry: LedgerEntry in runtime.economy_service.get_entries():
if entry.source_entity_id==room.instance_id and entry.category==&"SERVICE_SALE": revenue+=entry.amount
expect(revenue==264 and runtime.economy_service.validate_ledger()[&"ok"], "facility receives exactly the customer's payment")
autonomous_flow(runtime,clock,room.instance_id,workplace.instance_id)
placement_boundaries(runtime,room.instance_id,workplace.instance_id)
runtime.free();clock.free();state.free()
print("Anri floor pricing: %d checks, %d failures" % [checks,failures]);quit(failures)
func autonomous_flow(runtime: Variant, clock: Variant, room_id: StringName, workplace_id: StringName) -> void:
for floor_y in [1,2]:
var built: CommandResult=runtime.submit_command(&"build_facility",{&"definition_id":&"guest_stairs",&"origin":Vector3i(-7,floor_y,0)})
expect(built.is_success(), "build public stairs for autonomous guest")
if not built.is_success(): return
clock.advance_minutes(runtime.definition_registry.get_facility(&"guest_stairs").build_minutes)
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":workplace_id,&"template_id":&"DAY"}).is_success(), "reassign manager for autonomous guest flow")
expect(runtime.submit_command(&"spawn_guest",{&"guest_id":&"anri_walker",&"guest_type_id":&"traveler",&"gold":1000,
&"options":{&"current_cell":Vector3i(-7,0,0),&"state":GuestRuntime.State.DECIDING,&"planned_exit_at":clock.absolute_minute+2000,
&"needs":{&"sleep":100,&"hunger":0,&"hygiene":0,&"medical":0,&"leisure":0,&"intimacy":0}}}).is_success(), "spawn guest without forcing destination, route or price")
var selected := false
var moving_save: Dictionary={}
var service_id: StringName=&""
for minute in 40:
clock.advance_one_minute()
var guest: GuestRuntime=runtime.guest_service.get_guest(&"anri_walker")
if guest==null: break
selected=selected or guest.target_facility_id==room_id
if guest.current_cell==Vector3i(-7,1,0) and moving_save.is_empty(): moving_save=runtime.create_snapshot()
if not guest.current_service_id.is_empty(): service_id=guest.current_service_id;break
expect(selected, "autonomous needs select Anri-adjusted premium lodging")
expect(not moving_save.is_empty(), "guest physically traverses public stairs")
expect(not service_id.is_empty(), "arrival automatically starts service")
if service_id.is_empty() or moving_save.is_empty(): return
var started: Dictionary=runtime.create_snapshot()
expect(runtime.facility_service.get_service(service_id).price_snapshot==264, "autonomous service freezes same 264G price as management quote")
expect(runtime.restore_snapshot(moving_save), "restore guest movement and Anri assignment together")
for minute in 40:
clock.advance_one_minute()
if not runtime.guest_service.get_guest(&"anri_walker").current_service_id.is_empty(): break
expect(runtime.create_snapshot()==started, "movement save replay reproduces adjusted service exactly")
for minute in 721:
clock.advance_one_minute()
if runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED: break
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED, "autonomous adjusted stay completes")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:anri_walker")==736, "autonomous guest pays exactly264G once")
var revenue:=0
for entry: LedgerEntry in runtime.economy_service.get_entries():
if entry.source_entity_id==room_id and entry.category==&"SERVICE_SALE": revenue+=entry.amount
expect(revenue==528 and runtime.economy_service.validate_ledger()[&"ok"], "both264G payments equal facility revenue with balanced ledger")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "completed autonomous guest and adjusted ledger remain saveable")
func placement_boundaries(runtime: Variant, room_id: StringName, upper_workplace_id: StringName) -> void:
var ground_id: StringName=&""
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id==&"warehouse" and facility.origin.y==0: ground_id=facility.instance_id
var transfer: CommandResult=runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":ground_id,&"template_id":&"NIGHT"})
expect(transfer.is_success(), "move manager to another floor with night shift")
expect(runtime.get_floor_price_multiplier(0)==12000 and runtime.get_floor_price_multiplier(2)==10000, "floor transfer removes old bonus and applies new bonus exactly once")
expect(runtime.get_facility_business_price(runtime.facility_service.get_instance(room_id))==220, "old floor quotes base lodging price after transfer")
var assigned: Dictionary=runtime.staff_service.create_snapshot()
var rejected: CommandResult=runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":room_id,&"template_id":&"NIGHT"})
expect(not rejected.is_success() and rejected.details.get(&"reason")==&"FACILITY_UNSTAFFED", "manager cannot bypass zero staff capacity")
expect(runtime.staff_service.create_snapshot()==assigned and runtime.get_floor_price_multiplier(0)==12000, "rejected placement preserves schedule and active floor bonus")
var demolition: CommandResult=runtime.submit_command(&"demolish_facility",{&"instance_id":ground_id})
expect(not demolition.is_success() and demolition.details.get(&"reason")==&"STAFF_ASSIGNED", "assigned manager prevents workplace demolition")
expect(runtime.facility_service.get_instance(ground_id)!=null and runtime.get_floor_price_multiplier(0)==12000, "failed demolition preserves workplace and bonus")
var saved: Dictionary=runtime.create_snapshot()
for repetition in 3:
expect(runtime.restore_snapshot(saved) and runtime.create_snapshot()==saved and runtime.get_floor_price_multiplier(0)==12000 and runtime.get_floor_price_multiplier(2)==10000, "repeated night-assignment load preserves exact state without stacking")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_anri",&"facility_id":upper_workplace_id,&"template_id":&"DAY"}).is_success(), "return manager to original floor")
expect(runtime.get_floor_price_multiplier(0)==10000 and runtime.get_facility_business_price(runtime.facility_service.get_instance(room_id))==264, "return transfer restores only original floor bonus")
+1
View File
@@ -0,0 +1 @@
uid://5my1upi2ch2t
@@ -0,0 +1,219 @@
extends SceneTree
## Candidate only; not compiled or executed. No new production tuning values.
## The only cash fixture is a disclosed ledger spend at 23:59. Production
## reception cost, build time and upkeep rates remain exactly unchanged.
class ExplicitHireCatalog:
extends MvpContentCatalog
var fixture_payload: Dictionary = {}
func staff_hire_payload(candidate_id: StringName) -> Dictionary:
return fixture_payload.duplicate(true) if candidate_id == &"heroine_anri" else super.staff_hire_payload(candidate_id)
class GateReasonFixture:
extends OperationsCoordinator
var reason: StringName = &"UNPAID_EXPENSES"
func can_start_construction_or_investment() -> Dictionary:
return {&"allowed":false,&"reason":reason,&"arrears_total":1}
var checks := 0
var failures := 0
var next_direct_id := 980_000
func _initialize() -> void:
call_deferred("run")
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok:
failures += 1
push_error("FREE_UNIQUE: " + label)
func direct_command(payload: Dictionary) -> SimulationCommand:
next_direct_id += 1
return SimulationCommand.new(next_direct_id,0,0,0,&"hire_staff_candidate",&"player",&"",payload)
func fixture() -> Dictionary:
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()
expect(runtime.initialize(clock,state,424242)[&"valid"],"canonical content initializes")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"],"real production Operations enabled")
var built: CommandResult = runtime.submit_command(&"build_facility",{&"definition_id":&"reception",&"origin":Vector3i(-6,0,0)})
expect(built.is_success(),"normal construction command creates reception")
if not built.is_success():
return {&"runtime":runtime,&"clock":clock,&"state":state,&"ok":false}
clock.advance_minutes(runtime.definition_registry.get_facility(&"reception").build_minutes)
clock.advance_minutes(1439-clock.absolute_minute)
var cash: int = runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT)
# Disclosed economic boundary fixture, not evidence of natural spending.
var spent: CommandResult = runtime.economy_service.transfer(runtime.PLAYER_CASH_ACCOUNT,&"CONSTRUCTION_EXPENSE",cash,clock.absolute_minute,&"CONSTRUCTION",&"",&"acceptance.free_unique.cash_fixture")
expect(spent.is_success(),"controlled ledger spend leaves no available cash")
clock.advance_one_minute()
expect(clock.absolute_minute == 1440 and runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT) == 0,"real first midnight reached with zero cash")
expect(runtime.upkeep_settlement.get_arrears_total() > 0 and runtime.operations_coordinator.get_arrears_total() == runtime.upkeep_settlement.get_arrears_total(),"actual approved upkeep produces reconciled unpaid balance")
expect(not runtime.operations_coordinator.is_terminal() and not runtime._operations_should_block_tick(),"ordinary unpaid Operations remain running")
return {&"runtime":runtime,&"clock":clock,&"state":state,&"ok":true}
func free_fixture(value: Dictionary) -> void:
value[&"runtime"].free()
value[&"clock"].free()
value[&"state"].free()
func rejected_atomic(runtime: Variant, payload: Dictionary, label: String) -> CommandResult:
var before: Dictionary = runtime.create_snapshot()
var result: CommandResult = runtime._execute_hire_staff_candidate(direct_command(payload))
expect(not result.is_success() and runtime.create_snapshot() == before,label + " rejects before any Runtime state mutation")
return result
func strict_factory_and_state_guards(runtime: Variant) -> void:
var paid_payload := {&"candidate_id":&"staff_tessa",&"staff_id":&"heroine_anri",&"signing_cost":0,&"daily_wage":0,&"employment_kind":&"UNIQUE",&"options":{&"employment_kind":&"UNIQUE",&"daily_wage":0}}
var paid := rejected_atomic(runtime,paid_payload,"forged free fields on paid Tessa")
expect(paid.details.get(&"reason") == &"ARREARS_REMAIN","paid canonical costs retain arrears gate")
for payload: Dictionary in [{&"candidate_id":&"heroine_amelia"},{&"candidate_id":&"rival_rai"},{&"candidate_id":true},{&"candidate_id":&"unknown",&"staff_id":&"heroine_anri",&"employment_kind":&"UNIQUE"}]:
rejected_atomic(runtime,payload,"unapproved identity cannot claim Anri exception")
for payload: Dictionary in [{&"candidate_id":&"heroine_anri",&"current_cell":Vector3i(99999,99999,0)},{&"candidate_id":&"heroine_anri",&"current_cell":"bad"},{&"candidate_id":&"heroine_anri",&"facility_id":&"absent-workplace"}]:
rejected_atomic(runtime,payload,"free cost cannot bypass placement or path validation")
var source_catalog: MvpContentCatalog = runtime.mvp_content_catalog
var canonical := source_catalog.staff_hire_payload(&"heroine_anri")
var alterations: Array[Dictionary] = []
for field: StringName in [&"signing_cost",&"daily_wage"]:
for invalid: Variant in [1,0.0,"0",false,null]:
var changed := canonical.duplicate(true)
changed[field] = invalid
alterations.append(changed)
for invalid: Variant in [1,0.0,"0",false,null]:
var changed := canonical.duplicate(true)
changed[&"options"][&"daily_wage"] = invalid
alterations.append(changed)
for fields: Dictionary in [{&"staff_id":&"staff_tessa"},{&"role_id":&"receptionist"},{&"adult_verified":false}]:
var changed := canonical.duplicate(true)
changed.merge(fields,true)
alterations.append(changed)
var generic := canonical.duplicate(true)
generic[&"options"][&"employment_kind"] = &"FREE_HIRE"
alterations.append(generic)
for changed: Dictionary in alterations:
var replacement := ExplicitHireCatalog.new()
expect(replacement.rebuild()[&"valid"],"factory override starts from otherwise valid catalog")
replacement.fixture_payload = changed
var before: Dictionary = runtime.create_snapshot()
runtime.mvp_content_catalog = replacement
var denied: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
runtime.mvp_content_catalog = source_catalog
expect(not denied.is_success() and runtime.create_snapshot() == before,"nonzero/mistyped/identity/employment/adult canonical payload cannot use exemption")
var invalid_catalog := ExplicitHireCatalog.new() # deliberately not rebuilt
invalid_catalog.fixture_payload = canonical
var invalid_before: Dictionary = runtime.create_snapshot()
runtime.mvp_content_catalog = invalid_catalog
var invalid_result: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
runtime.mvp_content_catalog = source_catalog
expect(not invalid_result.is_success() and runtime.create_snapshot() == invalid_before,"invalid catalog cannot qualify despite a copied free payload")
# Injected safety boundaries are executor guards only, not valid-save or
# normal-production fixture claims. Restore fields before canonical snapshots.
var coordinator: OperationsCoordinator = runtime.operations_coordinator
for field: String in ["pause_requested","_external_quarantined"]:
var previous: Variant = coordinator.get(field)
var staff_before: Dictionary = runtime.staff_service.create_snapshot()
var economy_before: Dictionary = runtime.economy_service.create_snapshot()
coordinator.set(field,true)
var denied: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
coordinator.set(field,previous)
expect(not denied.is_success() and runtime.staff_service.create_snapshot() == staff_before and runtime.economy_service.create_snapshot() == economy_before,"paused/quarantined Operations do not permit free joining")
var prior_failure: bool = runtime._operations_runtime_failure_latched
runtime._operations_runtime_failure_latched = true
var failure_denied: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
runtime._operations_runtime_failure_latched = prior_failure
expect(not failure_denied.is_success(),"latched Runtime failure never hides behind free expense")
var reason_fixture := GateReasonFixture.new()
runtime.operations_coordinator = reason_fixture
var canonical_free: bool = runtime._is_approved_free_unique_hire(&"heroine_anri",canonical)
expect(canonical_free,"actual canonical approved payload qualifies for exact free policy")
for reason: StringName in [&"BANKRUPT",&"EXTERNAL_ROLLBACK_FAILED",&"FUTURE_BLOCK"]:
reason_fixture.reason = reason
var denied: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
expect(not denied.is_success() and denied.details.get(&"reason") == reason,"only explicit unpaid reasons may be exempted")
runtime.operations_coordinator = coordinator
var unpaid_expenses_before: Dictionary = runtime.create_snapshot()
reason_fixture.reason = &"UNPAID_EXPENSES"
runtime.operations_coordinator = reason_fixture
var zero_cost: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
runtime.operations_coordinator = coordinator
expect(zero_cost.is_success() and zero_cost.details.get(&"employment_kind") == &"UNIQUE","explicit UNPAID_EXPENSES gate permits canonical zero-cost joining")
expect(runtime.restore_snapshot(unpaid_expenses_before) and runtime.create_snapshot() == unpaid_expenses_before,"synthetic gate proof restores the original valid unpaid Runtime")
var callback := Callable(runtime,"_on_staff_registered")
expect(runtime.staff_service.staff_registered.is_connected(callback),"normal employee projection callback exists")
if runtime.staff_service.staff_registered.is_connected(callback):
var before: Dictionary = runtime.create_snapshot()
runtime.staff_service.staff_registered.disconnect(callback)
var failed: CommandResult = runtime._execute_hire_staff_candidate(direct_command({&"candidate_id":&"heroine_anri"}))
runtime.staff_service.staff_registered.connect(callback)
expect(not failed.is_success() and runtime.create_snapshot() == before,"post-creation projection failure atomically rolls free Staff/Shift/Path/history/ledger back")
func run() -> void:
var current := fixture()
if not current[&"ok"]:
free_fixture(current)
quit(failures)
return
var runtime: Variant = current[&"runtime"]
var clock: Variant = current[&"clock"]
strict_factory_and_state_guards(runtime)
var economy_before: Dictionary = runtime.economy_service.create_snapshot()
var arrears_before: int = runtime.upkeep_settlement.get_arrears_total()
var key: StringName = &"acceptance.approved_anri.unpaid"
var joined: CommandResult = runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"heroine_anri",&"employment_kind":&"FREE_HIRE",&"daily_wage":999,&"signing_cost":999},&"player",&"heroine_anri",75,-1,key)
expect(joined.is_success(),"zero-cash unpaid player joins approved Anri through real command queue")
if not joined.is_success():
free_fixture(current)
quit(failures)
return
var anri: StaffRuntime = runtime.staff_service.get_staff(&"heroine_anri")
expect(anri != null and anri.employment_kind == StaffRuntime.EmploymentKind.UNIQUE and anri.daily_wage == 0,"caller-paid aliases cannot change canonical free UNIQUE employee")
expect(joined.details.get(&"employment_kind") == &"UNIQUE" and joined.details.get(&"signing_cost") == 0 and joined.details.get(&"daily_wage") == 0,"successful public result describes actual zero-cost UNIQUE contract")
expect(runtime.economy_service.create_snapshot() == economy_before and runtime.upkeep_settlement.get_arrears_total() == arrears_before,"free recruitment neither spends cash nor forgives debt")
var hire_events := 0
for event: SimulationDomainEvent in runtime.domain_events.recent_events():
if event.event_type == &"STAFF_CANDIDATE_HIRED" and &"heroine_anri" in event.entity_ids:
hire_events += 1
expect(event.payload.get(&"employment_kind") == &"UNIQUE" and event.payload.get(&"signing_cost") == 0,"one emitted hire event matches canonical mode and amount")
expect(hire_events == 1,"one successful joining event")
var committed: Dictionary = runtime.create_snapshot()
var resent: CommandResult = runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"heroine_anri"},&"player",&"heroine_anri",75,-1,key)
expect(not resent.is_success() and resent.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY and runtime.create_snapshot() == committed,"same queued request cannot join or charge twice")
var stored: CommandResult = runtime.command_queue.get_result_by_idempotency_key(key)
expect(stored != null and stored.is_success() and stored.is_replay and stored.details.get(&"employment_kind") == &"UNIQUE","persisted result replay retains corrected UNIQUE mode")
rejected_atomic(runtime,{&"candidate_id":&"heroine_anri"},"different request cannot duplicate employee")
expect(not runtime.staff_service.remove_staff(&"heroine_anri",&"SOLD").is_success() and not runtime.staff_service.retire_staff(&"heroine_anri").is_success() and runtime.create_snapshot() == committed,"free joining retains original sale/retirement protection")
var copy := fixture_shell()
var resumed: Variant = copy[&"runtime"]
expect(resumed.restore_snapshot(committed) and resumed.create_snapshot() == committed,"new Runtime instance restores complete unpaid joined state exactly")
var replay: CommandResult = resumed.command_queue.get_result_by_idempotency_key(key)
expect(replay != null and replay.details.get(&"employment_kind") == &"UNIQUE" and resumed.get_staff_management_report()[&"staff"][0][&"daily_wage"] == 0,"load retains queue result mode and management zero upkeep")
var corrupted := committed.duplicate(true)
for row: Dictionary in corrupted[&"systems"][&"runtime"][&"staff"][&"staff"]:
if row[&"staff_id"] == "heroine_anri":
row[&"employment_kind"] = "FREE_HIRE"
expect(not resumed.restore_snapshot(corrupted) and resumed.create_snapshot() == committed,"saved employee cannot be downgraded to removable ordinary hire")
free_fixture(copy)
clock.advance_minutes(2880-clock.absolute_minute)
expect(runtime.operations_coordinator.get_day_summary(2)[&"category_totals"][&"wages"] == 0 and runtime.staff_service.get_daily_payroll_total() == 0,"following actual midnight creates no unique salary")
clock.advance_minutes(4320-clock.absolute_minute)
expect(runtime.operations_coordinator.is_terminal(),"actual third operating loss reaches existing BANKRUPT")
var terminal: Dictionary = runtime.create_snapshot()
var denied: CommandResult = runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"heroine_anri"})
expect(not denied.is_success() and denied.details.get(&"reason") == &"BANKRUPT" and runtime.create_snapshot() == terminal,"terminal normal API stays blocked before command acceptance")
free_fixture(current)
print("APPROVED_FREE_UNIQUE checks=%d failures=%d" % [checks,failures])
quit(failures)
func fixture_shell() -> Dictionary:
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()
expect(runtime.initialize(clock,state,424242)[&"valid"],"separate restore Runtime initializes")
clock.set_paused(true)
return {&"runtime":runtime,&"clock":clock,&"state":state}
@@ -0,0 +1 @@
uid://c3emkf34nje4k
+88
View File
@@ -0,0 +1,88 @@
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 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();runtime.initialize(clock,state,42608);clock.set_paused(true)
expect(runtime.submit_command(&"build_facility",{&"definition_id":&"chapel",&"origin":Vector3i(-6,0,0)}).is_success(),"build normal catalog chapel")
clock.advance_minutes(180)
var chapel:FacilityInstance
for f:FacilityInstance in runtime.facility_service.get_instances():
if f.definition_id==&"chapel":chapel=f
if chapel==null:expect(false,"chapel exists");quit(failures);return
var facility_id:StringName=chapel.instance_id
expect(runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"staff_uma"}).is_success(),"hire real cleric")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"staff_uma",&"facility_id":facility_id,&"template_id":&"DAY"}).is_success(),"assign cleric")
clock.advance_minutes(20)
expect(chapel.active_staff_ids==[&"staff_uma"],"actual cleric attendance opens chapel")
for index in 9:
var guest_id:=StringName("chapel_capacity_%d"%index)
expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":guest_id,&"gold":100}).is_success(),"fund controlled capacity payer")
expect(runtime.submit_command(&"create_service_request",{&"request_id":StringName("worship_%d"%index),&"facility_id":facility_id,&"guest_ids":[guest_id],&"service_type":&"worship"}).is_success(),"request capacity worship")
if index==7:clock.advance_one_minute()
clock.advance_one_minute()
expect(runtime.facility_service.get_active_services(facility_id).size()==8 and chapel.queue_entries.size()==1,"eight simultaneous customers and one separate waiting request")
var occupied:Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(occupied) and runtime.create_snapshot()==occupied,"full chapel and waiting queue restore exactly")
var services:Array=runtime.facility_service.get_active_services(facility_id)
var service_id:StringName=services[0].service_id
var elapsed:int=services[0].elapsed_minutes
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"staff_uma",&"facility_id":&"",&"template_id":&"DAY"}).is_success(),"normal unassignment removes required cleric")
clock.advance_minutes(2)
var interrupted:ServiceInstance=runtime.facility_service.get_service(service_id)
expect(interrupted.state==ServiceInstance.State.WAITING_FOR_STAFF and interrupted.elapsed_minutes==elapsed,"cleric loss pauses worship without consuming service time")
expect(state.get_resource(&"faith")==0 and runtime.economy_service.get_balance(&"GUEST_WALLET:chapel_capacity_0")==100,"interrupted worship grants no faith and takes no donation")
var paused:Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(paused) and runtime.create_snapshot()==paused,"interrupted services and grace deadline restore together")
clock.advance_minutes(6)
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.FAILED,"continued absence fails worship after existing replacement grace")
expect(state.get_resource(&"faith")==0 and runtime.economy_service.get_balance(&"GUEST_WALLET:chapel_capacity_0")==100,"failed worship grants no reward or charge")
expect(runtime.restore_snapshot(occupied),"restore staffed capacity scenario")
var exceeded:=false
for minute in 125:
clock.advance_one_minute()
exceeded=exceeded or runtime.facility_service.get_active_services(facility_id).size()>8
expect(not exceeded,"queue continuation never exceeds eight simultaneous users")
expect(state.get_resource(&"faith")==9 and runtime.progression_service.metrics[&"faith_lifetime"]==9,"all nine requests complete with nine faith total")
var revenue:=0
for entry:LedgerEntry in runtime.economy_service.get_entries():
if entry.category==&"SERVICE_SALE" and entry.source_entity_id==facility_id:revenue+=entry.amount
expect(revenue==180 and runtime.economy_service.validate_ledger()[&"ok"],"nine donations produce180G with balanced ledger")
expect(runtime.restore_snapshot(runtime.create_snapshot()),"finished capacity services and Faith remain saveable")
# Start a separate normal operating game; enabling midway is invalid by design.
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,42609);clock.set_paused(true)
var enabled:Dictionary=runtime.enable_production_operations()
expect(enabled[&"ok"],"enable normal arrivals and daily closing from new game")
if not enabled[&"ok"]:runtime.free();clock.free();state.free();quit(failures);return
expect(runtime.submit_command(&"build_facility",{&"definition_id":&"chapel",&"origin":Vector3i(-6,0,0)}).is_success(),"construct chapel in normal operating game")
clock.advance_minutes(180)
for f:FacilityInstance in runtime.facility_service.get_instances():
if f.definition_id==&"chapel":facility_id=f.instance_id
expect(runtime.submit_command(&"hire_staff_candidate",{&"candidate_id":&"staff_uma"}).is_success(),"hire cleric in normal operating game")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"staff_uma",&"facility_id":facility_id,&"template_id":&"DAY"}).is_success(),"staff chapel in normal operating game")
clock.advance_minutes(1439-clock.absolute_minute)
var before_close:Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(before_close),"chapel23:59 operating state restores")
clock.advance_one_minute()
var first_day:Dictionary=runtime.operations_coordinator.get_day_summary(1)
expect(not first_day.is_empty() and first_day[&"category_totals"][&"maintenance"]==3,"first partial ownership day charges prorated chapel upkeep")
clock.advance_minutes(1439)
var second_close:Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(second_close),"second operating day saves before settlement")
clock.advance_one_minute()
var second_day:Dictionary=runtime.operations_coordinator.get_day_summary(2)
expect(not second_day.is_empty() and second_day[&"category_totals"][&"maintenance"]==6,"full ownership day charges approved6G chapel upkeep")
var all_sales:=0
for entry:LedgerEntry in runtime.economy_service.get_entries():
if entry.category==&"SERVICE_SALE" and entry.source_entity_id==facility_id:all_sales+=entry.amount
expect(all_sales>0 and all_sales==int(state.get_resource(&"faith"))*20 and runtime.progression_service.metrics[&"faith_lifetime"]==state.get_resource(&"faith"),"two-day natural operations reconcile donations with Faith and lifetime progress")
expect(runtime.economy_service.validate_ledger()[&"ok"] and not runtime.get_operations_report()[&"runtime_failure_latched"],"two-day chapel operation closes without ledger or runtime failure")
expect(runtime.restore_snapshot(runtime.create_snapshot()),"second chapel close restores without repeating settlement")
runtime.free();clock.free();state.free()
print("Chapel boundaries: %d checks, %d failures"%[checks,failures]);quit(failures)
+1
View File
@@ -0,0 +1 @@
uid://delupsod4ws8p
+60
View File
@@ -0,0 +1,60 @@
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 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();runtime.initialize(clock,state,42606);clock.set_paused(true)
var paths: Array[String]=FacilityCatalog.FACILITY_PATHS+FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS+FacilityCatalog.RESCUE_FACILITY_PATHS+FacilityCatalog.ORIGINAL_FACILITY_PATHS
expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"],"staged chapel definition passes registry validation")
var definition: FacilityDefinition=runtime.definition_registry.get_facility(&"chapel")
expect(definition!=null and definition.footprint==Vector2i(4,3) and definition.layer==0,"original chapel dimensions and front layer")
expect(definition.construction_cost=={&"gold":1200,&"materials":12} and definition.build_minutes==180 and definition.maintenance_gold==6,"approved chapel construction and daily upkeep")
expect(definition.customer_capacity==8 and definition.staff_min==1 and definition.staff_max==2 and definition.required_roles==[&"cleric"],"approved dedicated customer capacity and cleric requirements")
expect(runtime.submit_command(&"build_facility",{&"definition_id":&"chapel",&"origin":Vector3i(-6,0,0)}).is_success(),"build staged chapel through normal construction command")
clock.advance_minutes(180)
var chapel: FacilityInstance
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id==&"chapel":chapel=facility
expect(chapel!=null and chapel.state==FacilityInstance.State.NO_STAFF,"chapel cannot operate without staff")
if chapel==null:runtime.free();clock.free();state.free();quit(failures);return
# Staffing fixture only; normal cleric recruitment is a separate integration.
expect(runtime.submit_command(&"set_facility_conditions",{&"facility_id":chapel.instance_id,&"conditions":{&"active_staff_ids":[&"cleric_fixture"]}}).is_success(),"controlled staffed service boundary")
expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":&"worship_payer",&"gold":100}).is_success(),"fund worship payer")
expect(runtime.submit_command(&"create_service_request",{&"request_id":&"worship_once",&"facility_id":chapel.instance_id,&"guest_ids":[&"worship_payer"],&"service_type":&"worship"}).is_success(),"request worship")
clock.advance_one_minute()
var active: Array=runtime.facility_service.get_active_services(chapel.instance_id)
expect(active.size()==1,"worship starts with approved staffing")
if active.is_empty():runtime.free();clock.free();state.free();quit(failures);return
var service_id: StringName=active[0].service_id
clock.advance_minutes(30)
var saved: Dictionary=runtime.create_snapshot()
expect(state.get_resource(&"faith")==0 and runtime.economy_service.get_balance(&"GUEST_WALLET:worship_payer")==100,"unfinished worship grants neither faith nor sale")
expect(runtime.restore_snapshot(saved),"restore worship in progress")
clock.advance_minutes(31)
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED,"worship completes after restore")
expect(state.get_resource(&"faith")==1 and runtime.progression_service.metrics[&"faith_lifetime"]==1,"faith inventory and lifetime rank metric gain one together")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:worship_payer")==80 and runtime.economy_service.validate_ledger()[&"ok"],"one20G donation with balanced ledger")
var completed: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(completed),"completed reward and ledger restore")
clock.advance_minutes(2)
expect(state.get_resource(&"faith")==1 and runtime.economy_service.get_balance(&"GUEST_WALLET:worship_payer")==80,"completed worship never repeats rewards after load")
expect(runtime.restore_snapshot(saved),"rewind to in-progress worship")
clock.advance_minutes(31)
expect(runtime.create_snapshot()==completed,"in-progress replay yields identical completion including faith and ledger")
expect(runtime.restore_snapshot(saved),"rewind for failed progression transaction")
runtime.progression_service.quarantined=true
clock.advance_minutes(31)
expect(runtime.facility_service.get_service(service_id).state!=ServiceInstance.State.COMPLETED,"unavailable progression prevents partial completion")
expect(state.get_resource(&"faith")==0 and runtime.economy_service.get_balance(&"GUEST_WALLET:worship_payer")==100,"faith failure rolls back guest payment and faith")
expect(runtime.economy_service.validate_ledger()[&"ok"],"failed faith reward leaves ledger balanced")
runtime.progression_service.quarantined=false
clock.advance_one_minute()
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED and state.get_resource(&"faith")==1 and runtime.economy_service.get_balance(&"GUEST_WALLET:worship_payer")==80,"recovered boundary retries with exactly one donation and reward")
runtime.free();clock.free();state.free()
print("Chapel service: %d checks, %d failures" % [checks,failures]);quit(failures)
+1
View File
@@ -0,0 +1 @@
uid://b5mh6cdy2ebrf
+13
View File
@@ -0,0 +1,13 @@
extends "res://tests/ui/test_cleaning_gameplay.gd"
## Recruitment values are pending; this is a registered character fixture,
## not evidence of Fine being available from the normal recruitment screen.
func cleaner_id() -> StringName: return &"heroine_fine"
func work_ticks() -> int: return 3
func hire_cleaner(_main: Control) -> void:
expect(runtime.submit_command(&"register_staff", {&"staff_id":cleaner_id(), &"role_id":&"cleaner", &"options":{&"employment_kind":&"UNIQUE", &"current_cell":Vector3i(-8,0,0)}}).is_success(), "register unique Fine test fixture")
var worker: StaffRuntime=runtime.staff_service.get_staff(cleaner_id())
expect(FacilityCleaningBridge.work_units_per_minute(worker)==3,"original Fine cleans at triple rate")
var ordinary:=StaffRuntime.new().configure(&"heroine_fine",&"cleaner")
expect(FacilityCleaningBridge.work_units_per_minute(ordinary)==1,"ordinary worker cannot inherit unique effect by name")
var different_role:=StaffRuntime.new().configure(cleaner_id(),&"porter",{&"employment_kind":&"UNIQUE"})
expect(FacilityCleaningBridge.work_units_per_minute(different_role)==1,"Fine identity does not accelerate unrelated roles")
@@ -0,0 +1 @@
uid://ckaflk5hmiirm
+7 -6
View File
@@ -37,7 +37,7 @@ func _test_baseline_catalog() -> void:
_expect(int(report[&"error_count"]) == 0, "baseline character data has no validation errors")
_expect(int(report[&"counts"]["guest_classes"]) == 8, "all eight guest classes are indexed")
_expect(int(report[&"counts"]["species"]) == 4, "all four independent species are indexed")
_expect(int(report[&"counts"]["staff_candidates"]) == 20, "all twenty free-hire candidates are indexed")
_expect(int(report[&"counts"]["staff_candidates"]) == 21, "twenty-one free-hire candidates are indexed")
_expect(int(report[&"counts"]["unique_characters"]) == 2, "one heroine and one rival are indexed")
_expect(catalog.get_guest_class_ids() == [
&"cleric", &"diplomat", &"mage", &"merchant", &"noble", &"outsider", &"rogue", &"warrior",
@@ -46,7 +46,7 @@ func _test_baseline_catalog() -> void:
&"beastfolk", &"demonfolk", &"elf", &"human",
], "species use stable lexical order")
_expect(catalog.get_staff_candidate_ids().front() == &"staff_akane", "candidate IDs are stable and namespaced")
_expect(catalog.get_staff_candidate_ids().back() == &"staff_tessa", "all candidate IDs participate in deterministic order")
_expect(catalog.get_staff_candidate_ids().back() == &"staff_uma", "all candidate IDs participate in deterministic order")
func _test_guest_combinations() -> void:
@@ -124,15 +124,16 @@ func _test_unique_pair_and_defensive_copies() -> void:
catalog.rebuild()
var heroine := catalog.get_entry(&"unique_characters", &"heroine_anri")
var rival := catalog.get_entry(&"unique_characters", &"rival_rai")
_expect(not heroine[&"mechanical_effects"].has(&"assigned_floor_patience_loss_multiplier_bp"), "unapproved patience substitute is absent from the original Anri definition")
_expect(heroine[&"character_kind"] == &"heroine" and rival[&"character_kind"] == &"rival", "unique characters have distinct mechanical kinds")
_expect(heroine[&"counterpart_id"] == &"rival_rai" and rival[&"counterpart_id"] == &"heroine_anri", "heroine and rival references are reciprocal")
_expect(heroine[&"adult_verified"] and rival[&"adult_verified"], "both relationship participants are explicitly adult-verified")
_expect(heroine[&"mechanical_effects"][&"assigned_floor_patience_loss_multiplier_bp"] == 9000, "heroine baseline effect is represented in integer basis points")
_expect(heroine[&"mechanical_effects"][&"assigned_floor_revenue_multiplier_bp"] == 12000, "original Anri floor revenue bonus is +20 percent in integer basis points")
heroine[&"display_name"] = "tampered"
heroine[&"mechanical_effects"][&"assigned_floor_patience_loss_multiplier_bp"] = 1
heroine[&"mechanical_effects"][&"assigned_floor_revenue_multiplier_bp"] = 1
var pristine := catalog.get_entry(&"unique_characters", &"heroine_anri")
_expect(pristine[&"display_name"] == "アンリ", "returned definitions cannot mutate catalog identity")
_expect(pristine[&"mechanical_effects"][&"assigned_floor_patience_loss_multiplier_bp"] == 9000, "returned nested data is also a defensive copy")
_expect(pristine[&"mechanical_effects"][&"assigned_floor_revenue_multiplier_bp"] == 12000, "returned nested data is also a defensive copy")
func _test_invalid_content_is_fail_closed() -> void:
@@ -242,7 +243,7 @@ func _test_unique_character_safety_contract() -> void:
_expect(&"effect_kind_mismatch" in codes, "effects cannot cross the heroine/rival capability boundary")
documents = _load_documents()
documents[&"unique_characters"]["entries"][0]["mechanical_effects"]["assigned_floor_patience_loss_multiplier_bp"] = 2_000
documents[&"unique_characters"]["entries"][0]["mechanical_effects"]["assigned_floor_revenue_multiplier_bp"] = 2_000
report = catalog.rebuild_from_documents(documents)
_expect(&"invalid_mechanical_effect" in _issue_codes(report[&"errors"]), "basis-point effects below the global multiplier floor are rejected")
+235 -29
View File
@@ -13,6 +13,7 @@ func _initialize() -> void:
func _run() -> void:
print("MVP: deterministic 14-day operations coordinator")
_test_root_snapshot_reuse()
_test_demand_schema_formula_and_clamp()
_test_boundary_order_leap_and_replay()
_test_fixed_point_arrivals_capacity_and_caps()
@@ -28,6 +29,11 @@ func _run() -> void:
_test_fair_arrivals_seed_and_id_high_water()
_test_snapshot_canonical_safety_and_derived_crosschecks()
_test_signal_reentrancy_and_deferred_pause()
_test_snapshot_ownership()
_test_canonical_sort_compatibility()
_test_checked_snapshot_encoding()
_test_variant_budget_boundaries()
_test_dictionary_key_budget_boundaries()
for service: RefCounted in _callback_services:
if service != null and service.has_method("configure_callbacks"):
service.call("configure_callbacks")
@@ -119,12 +125,12 @@ func _test_boundary_order_leap_and_replay() -> void:
)
var result := service.advance_to(1_800)
_expect(result.is_success() and service.current_minute == 1_800, "one leap advances through every crossed operating boundary")
_expect(result.details[&"boundary_counts"][&"daily_closes"] == 1, "leap performs the crossed 06:00 operating-day close exactly once")
_expect(result.details[&"boundary_counts"][&"daily_closes"] == 1, "leap performs the crossed midnight close exactly once")
_expect(result.details[&"boundary_counts"][&"morning_boundaries"] == 2, "leap performs both crossed 06:00 morning boundaries")
_expect(result.details[&"boundary_counts"][&"demand_updates"] == 30, "leap never thins 60-minute demand updates")
_expect(result.details[&"boundary_counts"][&"arrival_boundaries"] == 60, "leap never thins 30-minute arrival planning")
_expect(order == ["morning:360:capture", "daily:1800:capture", "morning:1800:capture"], "06:00 closes the prior operating day before morning work")
_expect(service.get_day_summaries().size() == 1 and service.get_morning_report(1)[&"minute"] == 360, "the first morning opens Day 1 and the next 06:00 closes it")
_expect(order == ["morning:360:capture", "daily:1440:capture", "morning:1800:capture"], "midnight closes the prior day before the next morning work")
_expect(service.get_day_summaries().size() == 1 and service.get_morning_report(1)[&"minute"] == 360, "the first morning opens Day 1 and the next midnight closes it")
var digest_before := service.deterministic_digest()
var replay := service.advance_to(1_800)
_expect(replay.is_success() and replay.is_replay, "same-time advance is an explicit replay success")
@@ -288,13 +294,13 @@ func _test_daily_close_summary_order_and_rolling_average() -> void:
return {&"ok": true}
)
var result := service.advance_to(8 * 1_440 + 360)
_expect(result.is_success() and service.get_day_summaries().size() == 8, "eight crossed 06:00 boundaries create exactly eight DaySummaries")
_expect(result.is_success() and service.get_day_summaries().size() == 8, "eight crossed midnight boundaries create exactly eight DaySummaries")
_expect(phases_by_day[1] == [&"capture", &"validate", &"apply"], "daily close callback uses fixed capture/validate/apply order")
var day1 := service.get_day_summary(1)
_expect(day1[&"net_profit"] == 90 and day1[&"previous_day_delta"] == 0, "Day 1 classified revenue and expense derive net profit with a zero baseline delta")
_expect(day1[&"category_totals"][&"revenue"] == 100 and day1[&"category_totals"][&"cost_of_goods"] == 10, "DaySummary retains classified income and expense totals")
_expect(day1[&"facility_profit_loss"][&"fac-01"][&"net_profit"] == 90, "DaySummary retains reconciled facility-level profit and loss")
_expect(day1[&"period_start_minute"] == 360 and day1[&"period_end_minute_exclusive"] == 1_800, "financial period is exactly the prior 06:00-to-06:00 operating day")
_expect(day1[&"period_start_minute"] == 0 and day1[&"period_end_minute_exclusive"] == 1_440, "financial period is exactly the prior midnight-to-midnight day")
var day2 := service.get_day_summary(2)
_expect(day2[&"net_profit"] == 180 and day2[&"previous_day_delta"] == 90, "previous-day delta compares adjacent closed days")
_expect(day2[&"seven_day_average"] == 135, "moving average uses all available days before seven closes")
@@ -329,13 +335,13 @@ func _test_daily_close_failure_and_ledger_imbalance_pause() -> void:
return {&"ok": true}
return {&"ok": false}
)
var failed := service.advance_to(1_800)
_expect(not failed.is_success() and service.current_minute == 1_799, "failed daily close leaves its 06:00 boundary pending for exact retry")
var failed := service.advance_to(1_440)
_expect(not failed.is_success() and service.current_minute == 1_439, "failed daily close leaves its 00:00 boundary pending for exact retry")
_expect(external[&"posted"] == 0 and external[&"rollbacks"] == 1, "partial external daily application rolls back to captured state")
_expect(service.get_day_summaries().is_empty(), "failed daily transaction creates no DaySummary or insolvency state")
var retried := service.advance_to(1_860)
_expect(retried.is_success() and service.get_day_summaries().size() == 1, "retry commits one and only one daily summary")
_expect(service.current_minute == 1_800, "imbalance pause stops a larger requested leap at the closing boundary")
_expect(service.current_minute == 1_440, "imbalance pause stops a larger requested leap at the closing boundary")
_expect(external[&"posted"] == 100 and int(external[&"apply_calls"]) == 2, "daily retry applies the external transaction once after rollback")
var summary := service.get_day_summary(1)
_expect(summary[&"reconciliation_difference"] == 7 and not summary[&"balanced"], "non-zero ledger difference is preserved instead of corrected")
@@ -343,9 +349,9 @@ func _test_daily_close_failure_and_ledger_imbalance_pause() -> void:
var alerts := service.get_alerts()
_expect(alerts.size() == 1 and alerts[0][&"code"] == &"LEDGER_IMBALANCE", "ledger imbalance emits one structured alert")
_expect(not alerts[0][&"automatic_correction"], "imbalance alert proves no balancing entry was invented")
_expect(not service.advance_to(1_801).is_success(), "pause request blocks further automatic advance")
_expect(not service.advance_to(1_441).is_success(), "pause request blocks further automatic advance")
_expect(service.acknowledge_pause().is_success() and not service.pause_requested, "caller can explicitly acknowledge a non-terminal diagnostic pause")
_expect(service.advance_to(1_801).is_success(), "advance resumes only after explicit acknowledgement")
_expect(service.advance_to(1_441).is_success(), "advance resumes only after explicit acknowledgement")
var malformed_external := {&"value": 0, &"rollback": 0}
var malformed := OperationsCoordinatorType.new()
@@ -365,7 +371,7 @@ func _test_daily_close_failure_and_ledger_imbalance_pause() -> void:
return {&"ok": true}
return {&"ok": false}
)
_expect(not malformed.advance_to(1_800).is_success(), "malformed successful-looking daily result is rejected")
_expect(not malformed.advance_to(1_440).is_success(), "malformed successful-looking daily result is rejected")
_expect(malformed_external[&"value"] == 0 and malformed_external[&"rollback"] == 1, "post-apply schema failure also rolls back external state")
_expect(malformed.get_day_summaries().is_empty(), "malformed result cannot partly commit coordinator state")
@@ -381,27 +387,27 @@ func _test_arrears_clear_and_three_close_bankruptcy() -> void:
if phase == &"validate":
return {&"ok": true, &"token": token}
if phase == &"apply":
return _daily_result(100, 100, int(arrears_by_day[int(payload[&"day"])]))
return _daily_result(100, 100 if int(payload[&"day"]) == 2 else 101, int(arrears_by_day[int(payload[&"day"])]))
return {&"ok": true}
)
service.advance_to(1_800)
service.advance_to(1_440)
_expect(service.get_arrears_total() == 50 and service.get_consecutive_insolvency_count() == 1, "first unpaid close records arrears and one warning")
var blocked := service.can_start_construction_or_investment()
_expect(not blocked[&"allowed"] and blocked[&"reason"] == &"ARREARS_REMAIN", "arrears prohibit new construction and ordinary investment")
service.advance_to(3_240)
service.advance_to(2_880)
_expect(service.get_arrears_total() == 0 and service.get_consecutive_insolvency_count() == 0, "fully paid next close clears the consecutive warning count")
_expect(service.can_start_construction_or_investment()[&"allowed"], "cleared arrears restore construction/investment eligibility")
service.advance_to(4_680)
service.advance_to(4_320)
_expect(service.get_consecutive_insolvency_count() == 1, "new arrears after clearance begin a fresh consecutive sequence")
service.advance_to(6_120)
_expect(service.get_consecutive_insolvency_count() == 2 and not service.is_terminal(), "second consecutive unpaid close warns but remains playable")
var terminal_result := service.advance_to(7_560)
_expect(terminal_result.is_success() and service.get_consecutive_insolvency_count() == 3, "third consecutive unpaid close commits before entering terminal state")
_expect(service.is_terminal() and service.get_terminal_state() == &"BANKRUPT", "three consecutive insolvent closes enter BANKRUPT terminal state")
_expect(service.current_minute == 7_560, "terminal transition stops precisely at the closing boundary")
service.advance_to(5_760)
_expect(service.get_consecutive_insolvency_count() == 2 and not service.is_terminal(), "second consecutive loss close warns but remains playable")
var terminal_result := service.advance_to(7_200)
_expect(terminal_result.is_success() and service.get_consecutive_insolvency_count() == 3, "third consecutive loss close commits before entering terminal state")
_expect(service.is_terminal() and service.get_terminal_state() == &"BANKRUPT", "three consecutive loss closes enter BANKRUPT terminal state")
_expect(service.current_minute == 7_200, "terminal transition stops precisely at the closing boundary")
var terminal_query := service.can_start_construction_or_investment()
_expect(not terminal_query[&"allowed"] and terminal_query[&"reason"] == &"BANKRUPT", "bankrupt terminal always prohibits construction and investment")
_expect(not service.advance_to(7_561).is_success(), "terminal operation rejects subsequent time advance")
_expect(not service.advance_to(7_201).is_success(), "terminal operation rejects subsequent time advance")
_expect(not service.acknowledge_pause().is_success(), "terminal state cannot be cleared through pause acknowledgement")
var warning_count := 0
var bankrupt_count := 0
@@ -509,8 +515,14 @@ func _test_snapshot_round_trip_migration_and_atomic_rejection() -> void:
_expect(not restored.restore_snapshot(future_version), "unknown future snapshot schema is rejected")
_expect(restored.create_snapshot() == before, "future-schema rejection is atomic")
var legacy := snapshot.duplicate(true)
var legacy: Dictionary = str_to_var(FileAccess.get_file_as_string("res://tests/mvp/fixtures/operations_v3_0600.txt"))
legacy[&"schema_version"] = 1
legacy[&"arrears_total"] = 0
legacy[&"consecutive_insolvency_count"] = 0
for row: Dictionary in legacy[&"day_summaries"]:
row[&"arrears_total"] = 0
row[&"settlement"][&"arrears_total"] = 0
row[&"consecutive_insolvency_count"] = 0
legacy.erase(&"alerts")
legacy.erase(&"demand_history")
var migrated := OperationsCoordinatorType.new()
@@ -736,7 +748,7 @@ func _test_origin_coverage_limits_and_legacy_population() -> void:
v2_snapshot.erase(field)
var v2_migrated := OperationsCoordinatorType.new()
_expect(v2_migrated.restore_snapshot(v2_snapshot), "v2 snapshot migrates using origin zero and derived high-water/fairness fields")
_expect(v2_migrated.create_snapshot()[&"schema_version"] == 3 and v2_migrated.validate_invariants()[&"ok"], "v2 migration re-emits a valid current-version snapshot")
_expect(v2_migrated.create_snapshot()[&"schema_version"] == OperationsCoordinatorType.SNAPSHOT_VERSION and v2_migrated.validate_invariants()[&"ok"], "v2 migration re-emits a valid current-version snapshot")
var downgraded_v2 := v2_source.create_snapshot()
downgraded_v2[&"schema_version"] = 2
downgraded_v2[&"origin_minute"] = 360
@@ -896,17 +908,17 @@ func _test_signal_reentrancy_and_deferred_pause() -> void:
return {&"ok": true}
)
var paused_result := paused.advance_to(1_860)
_expect(paused_result.is_success() and paused.current_minute == 1_800 and paused.pause_requested, "ledger imbalance commits the 06:00 close then stops a larger leap")
_expect(paused.get_demand_history()[-1][&"minute"] == 1_740 and paused.get_arrival_reports()[-1][&"minute"] == 1_770, "paused 06:00 close runs no same-minute morning, demand or arrival work")
_expect(paused_result.is_success() and paused.current_minute == 1_440 and paused.pause_requested, "ledger imbalance commits the 00:00 close then stops a larger leap")
_expect(paused.get_demand_history()[-1][&"minute"] == 1_380 and paused.get_arrival_reports()[-1][&"minute"] == 1_410, "paused 00:00 close runs no same-minute morning, demand or arrival work")
var paused_snapshot := paused.create_snapshot()
var resumed := OperationsCoordinatorType.new()
_expect(resumed.restore_snapshot(paused_snapshot) and resumed.validate_invariants()[&"ok"], "paused partial boundary snapshot retains a valid deferred suffix")
_expect(resumed.acknowledge_pause().is_success(), "explicit acknowledgement releases only the ledger diagnostic pause")
var resumed_result := resumed.advance_to(1_800)
_expect(resumed_result.is_success() and resumed.get_morning_report(2)[&"minute"] == 1_800 and resumed.get_demand_history()[-1][&"minute"] == 1_800 and resumed.get_arrival_reports()[-1][&"minute"] == 1_800, "acknowledged replay completes the deferred morning, demand and arrival suffix")
var resumed_result := resumed.advance_to(1_440)
_expect(resumed_result.is_success() and resumed.get_morning_report(2).is_empty() and resumed.get_demand_history()[-1][&"minute"] == 1_440 and resumed.get_arrival_reports()[-1][&"minute"] == 1_440, "acknowledged replay completes the deferred demand and arrival suffix without morning work")
_expect(not resumed_result.is_replay and resumed_result.details[&"deferred_completed"], "deferred completion is reported as new work rather than a side-effect-free replay")
var resumed_digest := resumed.deterministic_digest()
resumed.advance_to(1_800)
resumed.advance_to(1_440)
_expect(resumed.deterministic_digest() == resumed_digest, "completed deferred boundary cannot duplicate daily, demand, or arrival work")
@@ -1052,3 +1064,197 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_snapshot_ownership() -> void:
var service := _deterministic_fixture(53)
service.advance_to(1800)
var before := service.create_snapshot()
var exported := service.create_snapshot()
_scramble_snapshot(exported)
_expect(service.create_snapshot() == before, "mutating every exported snapshot container cannot alter live operations history")
var before_copy := before.duplicate(true)
service.advance_to(3240)
var resumed := _deterministic_fixture(53)
_expect(resumed.restore_snapshot(before), "earlier snapshot remains restorable after live history advances")
resumed.advance_to(3240)
_expect(resumed.create_snapshot() == service.create_snapshot() and before == before_copy, "snapshot restore and replay preserve owned history and deterministic result")
func _scramble_snapshot(value: Variant) -> void:
if value is Dictionary:
for key: Variant in value.keys():
_scramble_snapshot(value[key])
value.clear()
elif value is Array:
for child: Variant in value:
_scramble_snapshot(child)
value.clear()
func _test_canonical_sort_compatibility() -> void:
var cases: Array = [
{10: "ten", 2: "two", -1: "negative", "a": {&"z": 3, "b": [1, &"text"]}},
{"あ": 1, "é": 2, "Z": 3, "a": 4, "🙂": 5, "": 6},
{&"nested": [{&"field": "value", &"empty": {}}, []], &"flag": true},
]
var service := _deterministic_fixture(17)
service.advance_to(1800)
cases.append(service.create_snapshot())
for candidate: Variant in cases:
var actual: Variant = OperationsCoordinatorType._to_snapshot_variant(candidate)
var expected: Variant = _reference_snapshot_variant(candidate)
_expect(JSON.stringify(actual, "", false, true) == JSON.stringify(expected, "", false, true), "canonical output preserves legacy bytes including insertion order")
var invalid := {1: "integer", "1": "string"}
_expect(not OperationsCoordinatorType._variant_tree_report(invalid)[&"ok"], "colliding canonical keys remain rejected before serialization")
func _reference_snapshot_variant(value: Variant) -> Variant:
match typeof(value):
TYPE_STRING_NAME:
return String(value)
TYPE_ARRAY:
var result: Array = []
for child: Variant in value:
result.append(_reference_snapshot_variant(child))
return result
TYPE_DICTIONARY:
var result: Dictionary = {}
var keys: Array = value.keys()
keys.sort_custom(func(a: Variant, b: Variant) -> bool: return str(a) < str(b))
for key: Variant in keys:
result[str(key)] = _reference_snapshot_variant(value[key])
return result
_:
return value
func _test_variant_budget_boundaries() -> void:
var report: Dictionary = OperationsCoordinatorType._variant_tree_report({"a": ["あ", 2]})
_expect(report[&"ok"] and report[&"nodes"] == 5 and report[&"string_length"] == 2, "tree budget counts dictionary keys and Unicode characters exactly")
var budget: Array[int] = [OperationsCoordinatorType.MAX_VARIANT_NODES - 1, 0]
_expect(OperationsCoordinatorType._inspect_variant_tree(null, budget, 0) == &"", "node budget accepts exact limit")
_expect(OperationsCoordinatorType._inspect_variant_tree(null, budget, 0) == &"MAX_VARIANT_NODES", "node budget rejects next node")
budget = [0, OperationsCoordinatorType.MAX_TOTAL_STRING_LENGTH - 1]
_expect(OperationsCoordinatorType._inspect_variant_tree("あ", budget, 0) == &"", "text budget accepts exact limit")
_expect(OperationsCoordinatorType._inspect_variant_tree("a", budget, 0) == &"TOTAL_STRING_BUDGET", "text budget rejects next character")
budget = [0, 0]
_expect(OperationsCoordinatorType._inspect_variant_tree(null, budget, OperationsCoordinatorType.MAX_VARIANT_DEPTH) == &"", "tree depth accepts exact limit")
_expect(OperationsCoordinatorType._inspect_variant_tree(null, budget, OperationsCoordinatorType.MAX_VARIANT_DEPTH + 1) == &"MAX_VARIANT_DEPTH", "tree depth rejects next level")
_expect(OperationsCoordinatorType._variant_tree_report("a".repeat(OperationsCoordinatorType.MAX_SINGLE_STRING_LENGTH + 1))[&"code"] == &"STRING_TOO_LONG", "single string limit remains enforced")
var oversized: Array = []
oversized.resize(OperationsCoordinatorType.MAX_SAFE_COLLECTION_SIZE + 1)
_expect(OperationsCoordinatorType._variant_tree_report(oversized)[&"code"] == &"COLLECTION_TOO_LARGE", "collection limit remains enforced")
_expect(OperationsCoordinatorType._variant_tree_report(INF)[&"code"] == &"NON_FINITE_NUMBER", "non-finite numbers remain rejected")
func _test_dictionary_key_budget_boundaries() -> void:
var budget: Array[int]=[0,0]
_expect(OperationsCoordinatorType._inspect_variant_tree({"a":null}, budget, OperationsCoordinatorType.MAX_VARIANT_DEPTH)==&"MAX_VARIANT_DEPTH", "dictionary key exceeds depth before counting key node")
_expect(budget==[1,0], "depth failure leaves only dictionary node counted")
budget=[OperationsCoordinatorType.MAX_VARIANT_NODES-1,0]
_expect(OperationsCoordinatorType._inspect_variant_tree({"a":null},budget,0)==&"MAX_VARIANT_NODES", "dictionary key enforces node limit before string budget")
budget=[0,OperationsCoordinatorType.MAX_TOTAL_STRING_LENGTH]
_expect(OperationsCoordinatorType._inspect_variant_tree({"a":null},budget,0)==&"TOTAL_STRING_BUDGET", "dictionary key enforces aggregate text limit")
budget=[0,0]
_expect(OperationsCoordinatorType._inspect_variant_tree({"a".repeat(OperationsCoordinatorType.MAX_SINGLE_STRING_LENGTH+1):null},budget,0)==&"STRING_TOO_LONG", "dictionary key enforces single string limit")
budget=[0,0]
_expect(OperationsCoordinatorType._inspect_variant_tree({12:null},budget,0)==&"" and budget==[3,2], "integer key uses canonical string length and original node accounting")
func _test_checked_snapshot_encoding() -> void:
var cases: Array = [
null, true, false, 42, -7, 1.25, &"name", "", "Japanese and Unicode: あ🙂",
{10: "ten", 2: "two", -1: "negative", "a": {&"z": 3, "b": [1, &"text"]}},
{"あ": 1, "é": 2, "Z": 3, "a": 4, "🙂": 5, "": 6},
{&"nested": [{&"field": "value", &"empty": {}}, []], &"flag": true},
]
var coordinator := _deterministic_fixture(17)
coordinator.advance_to(1800)
cases.append(coordinator.create_snapshot())
for candidate: Variant in cases:
var encoded: Dictionary = OperationsCoordinatorType._snapshot_tree_report(candidate)
_expect(encoded[&"ok"], "checked encoder accepts safe scalar or tree")
_expect(JSON.stringify(encoded.get(&"value"), "", false, true) == JSON.stringify(_reference_snapshot_variant(candidate), "", false, true), "checked encoder retains legacy bytes and key insertion order")
var invalid_cases: Array = [
[INF, &"NON_FINITE_NUMBER"], [NAN, &"NON_FINITE_NUMBER"],
[{1: "integer", "1": "string"}, &"DUPLICATE_CANONICAL_KEY"],
[{Vector2i(1,2): 1}, &"INVALID_DICTIONARY_KEY"],
[Vector3i(1,2,3), &"UNSAVEABLE_VARIANT"],
["a".repeat(OperationsCoordinatorType.MAX_SINGLE_STRING_LENGTH + 1), &"STRING_TOO_LONG"],
]
var oversized: Array = []
oversized.resize(OperationsCoordinatorType.MAX_SAFE_COLLECTION_SIZE + 1)
invalid_cases.append([oversized, &"COLLECTION_TOO_LARGE"])
var deep: Variant = null
for depth in OperationsCoordinatorType.MAX_VARIANT_DEPTH + 1:
deep = [deep]
invalid_cases.append([deep, &"MAX_VARIANT_DEPTH"])
# A later invalid value must not reorder which historical error is surfaced.
invalid_cases.append([{"z": INF, "a": Vector2i.ZERO}, &"NON_FINITE_NUMBER"])
for pair: Array in invalid_cases:
var result: Dictionary = OperationsCoordinatorType._snapshot_tree_report(pair[0])
_expect(not result[&"ok"] and result[&"code"] == pair[1] and not result.has(&"value"), "checked encoding rejects unsafe tree without publishing a partial copy")
var exact := OperationsCoordinatorType._snapshot_tree_report({"a": ["あ", 2]})
_expect(exact[&"nodes"] == 5 and exact[&"string_length"] == 2, "encoding counts canonical dictionary keys and Unicode characters")
for encode: bool in [false, true]:
var budget: Array[int] = [OperationsCoordinatorType.MAX_VARIANT_NODES - 1, 0]
var issues: Array[StringName] = []
OperationsCoordinatorType._visit_snapshot_tree(null, budget, 0, issues, encode)
_expect(issues.is_empty(), "both visitor modes accept exact node budget")
OperationsCoordinatorType._visit_snapshot_tree(null, budget, 0, issues, encode)
_expect(issues == [&"MAX_VARIANT_NODES"], "both visitor modes reject excess node budget")
budget = [0, OperationsCoordinatorType.MAX_TOTAL_STRING_LENGTH - 1]
issues.clear()
OperationsCoordinatorType._visit_snapshot_tree("あ", budget, 0, issues, encode)
_expect(issues.is_empty(), "both visitor modes accept exact text budget")
OperationsCoordinatorType._visit_snapshot_tree("a", budget, 0, issues, encode)
_expect(issues == [&"TOTAL_STRING_BUDGET"], "both visitor modes reject excess text budget")
var source := {&"nested": [{&"value": 7}]}
var output: Dictionary = OperationsCoordinatorType._snapshot_tree_report(source)[&"value"]
source[&"nested"][0][&"value"] = 8
_expect(output["nested"][0]["value"] == 7, "encoder detaches nested source containers")
output["nested"].clear()
_expect(source[&"nested"].size() == 1, "mutating encoded output cannot change live input")
func _compare_root_snapshot_reuse(source: Dictionary, prefix: Dictionary = {}):
var cache := {}
var first := OperationsCoordinatorType._snapshot_root_report(source,cache,true)
var old_first := OperationsCoordinatorType._variant_tree_report(source)
_expect(first[&"ok"]==old_first[&"ok"] and first[&"code"]==old_first[&"code"],"initial validation preserves error precedence")
if not first[&"ok"]:return
_expect(first[&"nodes"]==old_first[&"nodes"] and first[&"string_length"]==old_first[&"string_length"],"initial node and text accounting identical")
var raw := prefix.duplicate(true)
raw.merge(source)
var before := OperationsCoordinatorType._snapshot_tree_report(raw)
var after := OperationsCoordinatorType._snapshot_root_report(raw,cache,false)
_expect(after[&"ok"]==before[&"ok"] and after[&"code"]==before[&"code"],"second pass preserves error precedence including aggregate overflow")
if after[&"ok"]:
_expect(after[&"nodes"]==before[&"nodes"] and after[&"string_length"]==before[&"string_length"],"second pass counts identical")
_expect(JSON.stringify(after[&"value"],"",false,true)==JSON.stringify(before[&"value"],"",false,true),"canonical bytes identical")
func _test_root_snapshot_reuse():
var cases: Array = [null,true,12,1.5,&"name",{"あ":[1,&"text"],2:{&"deep":false}},INF,NAN,{1:1,"1":2},{Vector2i.ZERO:1},Vector3i.ZERO,"a".repeat(4097),{"z":INF,"a":Vector2i.ZERO}]
var deep: Variant = null
for i in 33:deep=[deep]
cases.append(deep)
var huge := [];huge.resize(20001);cases.append(huge)
for value in cases:
_compare_root_snapshot_reuse({&"arrival_reports":value})
_compare_root_snapshot_reuse({1:1,"1":2})
_compare_root_snapshot_reuse({Vector2i.ZERO:1})
_compare_root_snapshot_reuse({"a".repeat(4097):1})
for i in 100:
_compare_root_snapshot_reuse({&"demand_history":[{&"z":i,"a":[i%7,&"文字",null]}],&"alerts":[i]}, {&"version":1,&"extra":[i]})
var text_values := []
text_values.resize(1999);text_values.fill("あ".repeat(4000))
_compare_root_snapshot_reuse({&"arrival_reports":text_values},{&"first":"b".repeat(4096)})
var nodes := [];var row := [];row.resize(19999)
for i in 99:nodes.append(row)
var prefix_nodes := [];prefix_nodes.resize(19999)
_compare_root_snapshot_reuse({&"demand_history":nodes},{&"first":prefix_nodes})
var owned_source := {&"alerts":[{&"text":"original"}]}
var cache := {}
OperationsCoordinatorType._snapshot_root_report(owned_source,cache,true)
var encoded := OperationsCoordinatorType._snapshot_root_report(owned_source,cache,false)
(encoded[&"value"]["alerts"] as Array).clear()
_expect(owned_source[&"alerts"].size()==1,"cached output never aliases live source")
@@ -0,0 +1,98 @@
extends SceneTree
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var service := OperationsCoordinator.new({&"start_minute": 360})
_bind(service, [-1, 0, -1, 1, -1, -1, -1], 0)
_expect(service.advance_to(1439).is_success() and service.get_day_summaries().is_empty(), "23:59 remains open")
var before_close := service.create_snapshot()
var boundary_restored := OperationsCoordinator.new()
_expect(boundary_restored.restore_snapshot(before_close), "23:59 save restores before the first close")
_bind(boundary_restored, [-1, 0, -1, 1, -1, -1, -1], 0)
boundary_restored.advance_to(1440)
_expect(service.advance_to(1440).is_success(), "first partial opening day closes at midnight")
_expect(boundary_restored.deterministic_digest() == service.deterministic_digest(), "save immediately before midnight closes and pays exactly once")
var legacy_open := before_close.duplicate(true)
legacy_open["schema_version"] = 3
legacy_open.erase("legacy_calendar_last_day")
legacy_open.erase("legacy_history_last_day")
var legacy_restored := OperationsCoordinator.new()
_expect(legacy_restored.restore_snapshot(legacy_open), "legacy save before its first close restores")
legacy_restored.advance_to(1500)
_expect(legacy_restored.get_day_summaries().is_empty(), "unclosed legacy first day is not retroactively settled at midnight")
legacy_restored.advance_to(1800)
_expect(legacy_restored.get_day_summary(1)[&"closed_at"] == 1800, "legacy first open day closes once at 06:00")
var day := service.get_day_summary(1)
_expect(day[&"closed_at"] == 1440 and day[&"period_start_minute"] == 0, "first day uses calendar midnight bounds")
_expect(service.get_consecutive_insolvency_count() == 1 and service.get_arrears_total() == 0, "a paid loss counts even without arrears")
service.advance_to(1800)
_expect(service.get_day_summaries().size() == 1 and not service.get_morning_report(2).is_empty(), "06:00 performs morning work without another close")
service.advance_to(2880)
_expect(service.get_consecutive_insolvency_count() == 0, "break-even resets loss streak")
service.advance_to(4320)
service.advance_to(5760)
_expect(service.get_consecutive_insolvency_count() == 0, "profit resets loss streak")
service.advance_to(8640)
var saved := service.create_snapshot()
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(saved), "two-loss snapshot restores")
var missing_alert := saved.duplicate(true)
missing_alert["alerts"].pop_back()
_expect(not restored.restore_snapshot(missing_alert) and restored.create_snapshot() == saved, "missing loss warning rejects atomically")
_bind(restored, [-1, 0, -1, 1, -1, -1, -1], 0)
service.advance_to(10200)
restored.advance_to(10200)
_expect(service.current_minute == 10080 and service.is_terminal(), "third loss stops a larger advance exactly at midnight")
_expect(service.deterministic_digest() == restored.deterministic_digest(), "save/load continuation preserves exact terminal outcome")
_expect(service.validate_invariants()[&"ok"], "loss history and terminal invariants reconcile")
var unpaid := OperationsCoordinator.new()
_bind(unpaid, [1, 1, 1, 1], 50)
unpaid.advance_to(5760)
_expect(not unpaid.is_terminal() and unpaid.get_consecutive_insolvency_count() == 0, "profitable closes with arrears never count as losses")
_expect(not unpaid.can_start_construction_or_investment()[&"allowed"], "unpaid wages still restrict new investment separately")
_test_legacy()
print("%s: %d checks" % ["PASS" if _failures == 0 else "FAIL", _checks])
quit(_failures)
func _test_legacy() -> void:
var old: Dictionary = str_to_var(FileAccess.get_file_as_string("res://tests/mvp/fixtures/operations_v3_0600.txt"))
var service := OperationsCoordinator.new()
_expect(service.restore_snapshot(old), "genuine pre-change v3 financial history restores")
_expect(service.get_day_summaries() == old["day_summaries"], "legacy closed financial records remain unchanged")
_expect(service.get_consecutive_insolvency_count() == 2, "historical unpaid streak remains auditable")
_bind(service, [-1, -1, -1, -1, -1], 0)
service.advance_to(4320)
_expect(service.get_day_summaries().size() == 2, "migration does not duplicate the still-open legacy day at midnight")
service.advance_to(4680)
_expect(service.get_day_summary(3)[&"period_start_minute"] == 3240 and service.get_consecutive_insolvency_count() == 1, "open legacy day closes once; new loss streak excludes old arrears count")
var snapshot := service.create_snapshot()
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(snapshot), "transition save restores")
_bind(restored, [-1, -1, -1, -1, -1], 0)
service.advance_to(5760)
restored.advance_to(5760)
_expect(service.get_day_summary(4)[&"period_start_minute"] == 4680 and service.get_day_summary(4)[&"closed_at"] == 5760, "18-hour transition has no gap or overlap")
_expect(service.deterministic_digest() == restored.deterministic_digest(), "migration replay is deterministic")
var before := restored.create_snapshot()
var bad := before.duplicate(true)
bad["legacy_calendar_last_day"] = 1
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == before, "inconsistent transition marker rejects atomically")
service.advance_to(8000)
_expect(service.current_minute == 7200 and service.is_terminal() and service.validate_invariants()[&"ok"], "migrated save ends on three new losses at midnight")
func _bind(service: OperationsCoordinator, profits: Array, arrears: int) -> void:
service.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply": return {&"ok": true, &"token": token}
var profit := int(profits[int(payload[&"day"]) - 1])
return {&"ok": true, &"category_totals": {&"revenue": 100 + profit, &"wages": 100}, &"facility_profit_loss": {}, &"anomalies": [], &"reconciliation_difference": 0, &"settlement": {&"arrears_total": arrears}})
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(message)
@@ -0,0 +1 @@
uid://bpr5lgs5yx5yb
+41
View File
@@ -0,0 +1,41 @@
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 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(); runtime.initialize(clock,state,42571); clock.set_paused(true)
runtime.submit_command(&"change_resources", {&"deltas": {&"gold": 5000, &"materials": 100}})
runtime.submit_command(&"update_progression_metrics", {&"metrics": {&"reputation": 25}})
expect(runtime.submit_command(&"set_world_tree_rank", {&"rank":2}).is_success(), "fixture unlocks underground by rank command")
for id: StringName in [&"standard_room", &"premium_room", &"staff_dormitory"]:
var definition: FacilityDefinition = runtime.definition_registry.get_facility(id)
var origin := Vector3i(2,-1,definition.layer)
var report: PlacementReport = runtime.construction_service.validate_placement(definition,origin)
expect(report.zone_mismatch_cells.is_empty() and not report.unexcavated_cells.is_empty(), "%s permits underground but still requires excavation" % id)
expect(not runtime.submit_command(&"build_facility", {&"definition_id":id,&"origin":origin}).is_success(), "unexcavated build rejected")
var wrong: PlacementReport = runtime.construction_service.validate_placement(definition,Vector3i(2,-1,1-definition.layer))
expect(not wrong.layer_mismatch_cells.is_empty(), "%s preserves original layer restriction" % id)
for x in range(0,10):
expect(runtime.submit_command(&"start_excavation", {&"cell":Vector3i(x,-1,0)}).is_success(), "normal command excavates underground footprint")
clock.advance_minutes(60)
for x in range(0,10): expect(runtime.world_service.is_excavated(Vector3i(x,-1,0)), "excavation completes")
for item: Array in [[&"standard_room",Vector3i(1,-1,0)],[&"premium_room",Vector3i(4,-1,0)],[&"staff_dormitory",Vector3i(1,-1,1)]]:
var report: PlacementReport = runtime.construction_service.validate_placement(runtime.definition_registry.get_facility(item[0]),item[1])
expect(report.can_build, "%s underground placement is valid" % item[0])
var result: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id":item[0],&"origin":item[1]})
expect(result.is_success(), "%s constructs in excavated underground: %s" % [item[0],result.message])
clock.advance_minutes(150)
for id: StringName in [&"standard_room", &"premium_room", &"staff_dormitory"]:
var found := false
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == id and facility.origin.y == -1: found = true
expect(found, "%s finishes as a real underground facility" % id)
expect(runtime.restore_snapshot(runtime.create_snapshot()), "underground rooms survive save restore")
runtime.free();clock.free();state.free()
print("Original room zones: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://dswpwd2gkbxnf
+48
View File
@@ -0,0 +1,48 @@
extends SceneTree
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func run() -> void:
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();runtime.initialize(clock,state,42501);clock.set_paused(true)
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"premium_room")
expect(definition != null and definition.footprint == Vector2i(4,1), "normal Runtime registers original-size premium room")
expect(definition.maintenance_gold == 5 and definition.customer_capacity == 2, "daily upkeep follows approved half-percent initial rule and people limit is explicit")
expect(runtime.submit_command(&"build_facility",{&"definition_id":&"reception",&"origin":Vector3i(-6,0,0)}).is_success(), "build supporting reception")
clock.advance_minutes(120)
var built: CommandResult = runtime.submit_command(&"build_facility",{&"definition_id":&"premium_room",&"origin":Vector3i(-6,2,0)})
expect(built.is_success(), "normal construction command accepts premium room")
if not built.is_success(): runtime.free();clock.free();state.free();quit(failures);return
clock.advance_minutes(150)
var id: StringName = &""
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id == &"premium_room": id = room.instance_id
expect(not id.is_empty(), "premium room completes into operating facility service")
if id.is_empty(): runtime.free();clock.free();state.free();quit(failures);return
expect(runtime.facility_service.get_instance(id).occupied_cells.size()==4, "operating room owns four original cells")
expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":&"premium_payer",&"gold":1000}).is_success(), "fund service fixture wallet")
var requested: CommandResult = runtime.submit_command(&"create_service_request",{&"request_id":&"premium_stay",&"facility_id":id,&"guest_ids":[&"premium_payer"],&"service_type":&"lodging"})
expect(requested.is_success(), "premium stay enters normal service queue at definition price")
clock.advance_one_minute()
var active: Array = runtime.facility_service.get_active_services(id)
expect(active.size()==1, "normal operating tick starts premium lodging")
if active.is_empty(): runtime.free();clock.free();state.free();quit(failures);return
var service_id: StringName = active[0].service_id
expect(active[0].duration_minutes==720 and active[0].price_snapshot==220, "premium lodging uses existing duration and price")
clock.advance_minutes(360)
var halfway: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(halfway), "mid-stay current-schema snapshot restores")
clock.advance_minutes(361)
var completed: ServiceInstance = runtime.facility_service.get_service(service_id)
expect(completed.state==ServiceInstance.State.COMPLETED, "restored premium stay completes")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:premium_payer")==780, "premium stay charges exactly 220 gold once")
expect(runtime.economy_service.validate_ledger()[&"ok"], "premium revenue balances the ledger")
clock.advance_minutes(1440-clock.absolute_minute)
expect(runtime.restore_snapshot(runtime.create_snapshot()), "midnight upkeep and premium room persist together")
runtime.free();clock.free();state.free()
print("Premium room integration: %d checks, %d failures" % [checks,failures]);quit(failures)
+1
View File
@@ -0,0 +1 @@
uid://doijy3o7nbtl2
+114
View File
@@ -0,0 +1,114 @@
extends SceneTree
var checks := 0
var failures := 0
var runtime: Variant
var clock: Variant
var state: Variant
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func build(id: StringName, origin: Vector3i) -> bool:
var result: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id":id,&"origin":origin})
expect(result.is_success(), "normal build %s: %s" % [id,result.message])
if not result.is_success(): return false
clock.advance_minutes(runtime.definition_registry.get_facility(id).build_minutes)
return true
func run() -> void:
runtime=load("res://autoload/simulation_runtime.gd").new()
clock=load("res://autoload/game_clock.gd").new();state=load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,42502);clock.set_paused(true)
if not build(&"reception",Vector3i(-6,0,0)) or not build(&"premium_room",Vector3i(-6,2,0)) or not build(&"guest_stairs",Vector3i(-7,1,0)) or not build(&"guest_stairs",Vector3i(-7,2,0)):
finish();return
var room_id: StringName = &""
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id==&"premium_room": room_id=room.instance_id
expect(runtime.get_facility_access_report(room_id)[&"reachable"], "normal stairs make premium room reachable from entrance")
var spawn: CommandResult = runtime.submit_command(&"spawn_guest",{&"guest_id":&"premium_walker",&"guest_type_id":&"traveler",&"gold":1000,
&"options":{&"current_cell":Vector3i(-7,0,0),&"state":GuestRuntime.State.DECIDING,&"planned_exit_at":clock.absolute_minute+2000,
&"needs":{&"sleep":100,&"hunger":0,&"hygiene":0,&"medical":0,&"leisure":0,&"intimacy":0}}})
expect(spawn.is_success(), "guest enters deciding state without forcing target or route")
var used_stairs := false
var selected_room := false
var service_id: StringName = &""
var moving_save := {}
for minute in 40:
clock.advance_one_minute()
var guest: GuestRuntime = runtime.guest_service.get_guest(&"premium_walker")
if guest==null: break
selected_room = selected_room or guest.target_facility_id==room_id
if guest.current_cell==Vector3i(-7,1,0):
used_stairs=true
if moving_save.is_empty(): moving_save=runtime.create_snapshot()
if not guest.current_service_id.is_empty():
service_id=guest.current_service_id;break
expect(selected_room, "highest sleep need selects premium room autonomously")
expect(used_stairs, "guest physically traverses first staircase landing")
expect(not service_id.is_empty(), "guest arrival automatically queues and starts lodging")
if service_id.is_empty():
var guest: GuestRuntime=runtime.guest_service.get_guest(&"premium_walker")
print("Guest diagnostic: ", guest.create_snapshot() if guest!=null else {})
finish();return
var started: Dictionary=runtime.create_snapshot()
expect(not moving_save.is_empty() and runtime.restore_snapshot(moving_save), "actual stair movement save restores")
for minute in 40:
clock.advance_one_minute()
if not runtime.guest_service.get_guest(&"premium_walker").current_service_id.is_empty(): break
expect(runtime.create_snapshot()==started, "stair-save replay reproduces arrival and queued service exactly")
clock.advance_minutes(360)
var mid: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(mid), "real guest and in-progress premium service restore together")
var elapsed: int=runtime.facility_service.get_service(service_id).elapsed_minutes
state.set_resource(&"mana",0)
clock.advance_minutes(15)
var paused: ServiceInstance=runtime.facility_service.get_service(service_id)
expect(paused.state==ServiceInstance.State.PAUSED and paused.elapsed_minutes==elapsed, "zero mana pauses actual guest lodging without consuming service time")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:premium_walker")==1000, "outage does not charge real lodging guest")
var disk_path := "user://saves/premium_guest_outage_%d" % Time.get_ticks_usec()
var outage: Dictionary=runtime.create_snapshot()
expect(SaveService.save_snapshot(disk_path,outage)[&"ok"], "write real guest outage snapshot to disposable save")
var saved := SaveService.load_snapshot(disk_path)
expect(saved[&"ok"] and runtime.restore_snapshot(saved[&"snapshot"]), "restore real guest, paused service and empty mana from disk")
SaveService.delete_save(disk_path)
state.set_resource(&"mana",100)
for minute in 370:
clock.advance_one_minute()
if runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED: break
expect(runtime.facility_service.get_service(service_id).state==ServiceInstance.State.COMPLETED, "autonomous premium stay completes after restored continuation")
expect(runtime.economy_service.get_balance(&"GUEST_WALLET:premium_walker")==780, "real guest pays one premium lodging charge")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "completed guest/service/path/economy crosslinks remain saveable")
clock.advance_minutes(maxi(1440-clock.absolute_minute,0))
expect(runtime.economy_service.validate_ledger()[&"ok"] and runtime.restore_snapshot(runtime.create_snapshot()), "autonomous lodging and upkeep survive midnight ledger close and restore")
capacity_flow()
finish()
func finish() -> void:
runtime.free();clock.free();state.free()
print("Premium guest flow: %d checks, %d failures" % [checks,failures]);quit(failures)
func capacity_flow() -> void:
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,42503);clock.set_paused(true)
if not build(&"reception",Vector3i(-6,0,0)) or not build(&"premium_room",Vector3i(-6,2,0)) or not build(&"guest_stairs",Vector3i(-7,1,0)) or not build(&"guest_stairs",Vector3i(-7,2,0)): return
var room_id: StringName=&""
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id==&"premium_room": room_id=room.instance_id
for index in 3:
var id:=StringName("premium_capacity_%d" % index)
expect(runtime.submit_command(&"spawn_guest",{&"guest_id":id,&"guest_type_id":&"traveler",&"gold":1000,
&"options":{&"current_cell":Vector3i(-7-index,0,0),&"state":GuestRuntime.State.DECIDING,&"planned_exit_at":clock.absolute_minute+2000,
&"needs":{&"sleep":100,&"hunger":0,&"hygiene":0,&"medical":0,&"leisure":0,&"intimacy":0}}}).is_success(), "spawn autonomous capacity guest")
var maximum := 0
var saw_queue := false
var exceeded := false
for minute in 60:
clock.advance_one_minute()
var using: int=runtime.facility_service.get_active_services(room_id).size()
maximum=maxi(maximum,using)
exceeded=exceeded or using>2
if using==2:
for guest: GuestRuntime in runtime.guest_service.get_guests():
if guest.target_facility_id==room_id and guest.state==GuestRuntime.State.QUEUING: saw_queue=true
expect(maximum==2 and not exceeded, "three autonomous guests never exceed two simultaneous lodging users")
expect(saw_queue, "third guest waits separately from the two using the room")
var crowded: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(crowded) and runtime.create_snapshot()==crowded, "occupied room and separate queue restore exactly")
@@ -0,0 +1 @@
uid://d1jwox0u5ar5x
+25 -2
View File
@@ -30,6 +30,7 @@ func _run() -> void:
_test_catalog_bootstrap_and_queries()
_test_progression_resource_and_guest_facts()
_test_catalog_staff_hire_transaction()
_test_unique_runtime_protection()
_test_rank_two_three_and_atomic_failure()
_test_v3_migration_and_v4_tamper_guards()
_test_twenty_four_hour_replay()
@@ -71,7 +72,7 @@ func _test_catalog_bootstrap_and_queries() -> void:
_expect(bool(report[&"valid"]), "MVP catalog report is valid")
_expect(int(report[&"counts"]["guest_classes"]) == 8, "catalog exposes eight Guest classes")
_expect(int(report[&"counts"]["species"]) == 4, "catalog exposes four independent species")
_expect(int(report[&"counts"]["staff_candidates"]) == 20, "catalog exposes twenty fixed Staff candidates")
_expect(int(report[&"counts"]["staff_candidates"]) == 21, "catalog exposes twenty-one fixed Staff candidates")
_expect(int(report[&"counts"]["unique_characters"]) == 2, "catalog exposes both unique characters")
var catalog_ids: Array[StringName] = _runtime.get_mvp_guest_definition_ids()
_expect(catalog_ids.size() == 32, "Runtime registers every class/species Guest definition")
@@ -382,6 +383,7 @@ func _test_v3_migration_and_v4_tamper_guards() -> void:
v3_runtime.erase(&"kitchen_production")
v3_runtime.erase(&"elevators")
v3_runtime.erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(v3_runtime)
v3_runtime[&"path"][&"schema_version"] = 3
v3_runtime[&"path"].erase(&"vertical_walk_blockers")
v3_runtime[&"path"].erase(&"vertical_walk_requires_connector")
@@ -402,7 +404,7 @@ func _test_v3_migration_and_v4_tamper_guards() -> void:
_expect(int(_runtime.progression_service.metrics[&"guest_lifetime"]) == 2, "v3 migration derives lifetime Guests from active plus persistent records")
_expect(_runtime.get_mvp_guest_definition_ids().size() == 32, "v3 migration registers the current 32 catalog definitions")
var migrated: Dictionary = _runtime.create_snapshot()
_expect(int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 9, "v3 migration emits canonical Runtime schema v9")
_expect(int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 13, "v3 migration emits canonical Runtime schema v13")
var digest := SimulationDigest.compute(migrated)
_expect(_runtime.restore_snapshot(migrated), "canonical v9 snapshot restores")
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "canonical v9 restore is exact")
@@ -505,3 +507,24 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_unique_runtime_protection() -> void:
_reset_runtime(82_017)
var staff_before: Dictionary = _runtime.staff_service.create_snapshot()
var economy_before: Dictionary = _runtime.economy_service.create_snapshot()
for id: StringName in _runtime.mvp_content_catalog.get_unique_character_ids():
var forged: CommandResult = _runtime.submit_command(&"register_staff", {
&"staff_id": id, &"role_id": &"receptionist", &"options": {&"employment_kind": &"FREE_HIRE"}})
_expect(not forged.is_success() and _runtime.staff_service.create_snapshot() == staff_before and _runtime.economy_service.create_snapshot() == economy_before, "reserved unique identity cannot bypass its protected recruitment contract")
# Generic legacy fixture checks UNIQUE behavior without inventing Anri's joining conditions.
var created: CommandResult = _runtime.submit_command(&"register_staff", {
&"staff_id": &"unique_runtime_fixture", &"role_id": &"receptionist",
&"options": {&"employment_kind": &"UNIQUE", &"daily_wage": 75, &"current_cell": Vector3i.ZERO}})
_expect(created.is_success() and _runtime.economy_service.create_snapshot() == economy_before, "unique runtime fixture registers without signing payment")
var report: Dictionary = _runtime.get_staff_management_report()
_expect(report[&"staff"].size() == 1 and report[&"staff"][0][&"daily_wage"] == 0, "normal staff panel report shows effective zero unique wage")
var before: Dictionary = _runtime.create_snapshot()
_expect(not _runtime.staff_service.retire_staff(&"unique_runtime_fixture").is_success() and _runtime.create_snapshot() == before, "unique retirement rejection preserves complete runtime including paths and ledger")
_expect(_runtime.restore_snapshot(before) and _runtime.create_snapshot() == before, "runtime unique snapshot restores exactly")
_expect(_runtime.get_staff_management_report()[&"staff"][0][&"daily_wage"] == 0 and _runtime.staff_service.get_daily_payroll_total() == 0, "restored runtime and UI both retain zero unique upkeep")
+81
View File
@@ -0,0 +1,81 @@
extends SceneTree
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("_run")
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok:
failures += 1
push_error(message)
func _run() -> void:
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()
expect(runtime.initialize(clock, state, 424242)[&"valid"], "initialize")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable natural operations")
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(-6, 0, 0)}).is_success(), "build reception")
clock.advance_minutes(120)
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"standard_room", &"origin": Vector3i(-6, 2, 0)}).is_success(), "build zero-demand room on supported floor")
clock.advance_minutes(60)
var reception: FacilityInstance
var room: FacilityInstance
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"reception": reception = facility
if facility.definition_id == &"standard_room": room = facility
expect(room != null and reception != null, "both facilities complete normally")
if room == null or reception == null:
runtime.free(); clock.free(); state.free(); quit(failures); return
# Only reserve depletion is a diagnostic input; no numeric generation policy
# is asserted by this test. Everything else advances through Runtime ticks.
state.set_resource(&"mana", 0)
# Pre-fix saves legitimately contain zero reserve with old powered flags.
var legacy: Dictionary = runtime.create_snapshot()
var legacy_data: Dictionary = legacy[&"systems"][&"runtime"]
legacy_data[&"runtime_schema_version"] = 9
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_data)
var source_copy := legacy.duplicate(true)
var old_reception_id := reception.instance_id
var old_room_id := room.instance_id
expect(runtime.restore_snapshot(legacy), "pre-upkeep zero-reserve save migrates with old facility flags")
expect(legacy == source_copy, "migration does not mutate its source save")
expect(state.get_resource(&"mana") == 0 and clock.absolute_minute == 540 and clock.paused, "legacy migration preserves empty reserve saved time and pause")
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 0, "migration does not charge pre-save ownership or mana consumption")
var no_admission: Dictionary = runtime.call("_operations_arrival_boundary", {}, OperationsCoordinator.PHASE_CAPTURE, null)
expect(no_admission[&"capacity_remaining"] == 0, "legacy empty reserve rejects admission even before first resumed tick")
var migrated: Dictionary = runtime.create_snapshot()
var invalid: Dictionary = migrated.duplicate(true)
invalid[&"game_state"][&"resources"][&"mana"] = -1
expect(not runtime.restore_snapshot(invalid) and runtime.create_snapshot() == migrated, "invalid negative-reserve save rejects without changing active state")
clock.advance_minutes(1)
reception = runtime.facility_service.get_instance(old_reception_id)
room = runtime.facility_service.get_instance(old_room_id)
expect(clock.absolute_minute == 541 and not reception.power_available, "depletion gates powered facilities outside ten-minute evaluation")
expect(not room.power_available and room.operation_state == FacilityInstance.OperationState.INACTIVE, "zero-demand room also stops")
var capture: Dictionary = runtime.call("_operations_arrival_boundary", {}, OperationsCoordinator.PHASE_CAPTURE, null)
expect(capture[&"ok"] and capture[&"capacity_remaining"] == 0, "arrival transaction exposes no admission capacity during outage")
var stopped: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(stopped), "outage restores through full runtime")
expect(runtime.create_snapshot() == stopped, "outage snapshot is exact")
clock.advance_minutes(9)
expect(not runtime.facility_service.get_instance(room.instance_id).power_available, "scheduled positive network output cannot bypass empty reserve")
var room_id := room.instance_id
var reception_id := reception.instance_id
runtime.facility_service.set_operational_status(room_id, {&"suspension_reason": &"DESTROYED"}, clock.absolute_minute)
state.set_resource(&"mana", 1)
clock.advance_minutes(1)
room = runtime.facility_service.get_instance(room_id)
reception = runtime.facility_service.get_instance(reception_id)
expect(reception.power_available, "reserve restoration restores actual network supply next minute")
expect(room.power_available and room.operation_state == FacilityInstance.OperationState.SUSPENDED and room.suspension_reason == &"DESTROYED", "power recovery does not reverse destruction suspension")
var recovered: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(recovered), "recovered runtime and persistent suspension restore")
expect(not runtime.get_operations_report()[&"runtime_failure_latched"], "depletion and recovery do not latch a runtime fault")
runtime.free()
clock.free()
state.free()
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
@@ -0,0 +1 @@
uid://brqwbggfrwla6
+20 -17
View File
@@ -1,7 +1,7 @@
extends SceneTree
const FOURTEEN_DAYS_MINUTES := 14 * SimulationKernel.MINUTES_PER_DAY
const RUNTIME_SNAPSHOT_VERSION := 9
const RUNTIME_SNAPSHOT_VERSION := 13
var _checks := 0
var _failures := 0
@@ -39,7 +39,7 @@ func _new_fixture(seed: int, enable_operations := true) -> Dictionary:
int(initial_snapshot[&"systems"][&"runtime"].get(
&"runtime_schema_version", -1
)) == RUNTIME_SNAPSHOT_VERSION,
"fixture emits Runtime snapshot schema v9"
"fixture emits Runtime snapshot schema v10"
)
if enable_operations:
var enabled: Dictionary = runtime.enable_production_operations()
@@ -80,6 +80,9 @@ func _advance_operations_boundary_slice(fixture: Dictionary, amount: int) -> int
clock.absolute_minute = target
clock.call("_sync_calendar_fields")
clock.snapshot_version += step
# Weather belongs to TIME_BOUNDARIES even when AI is skipped.
runtime.call("_advance_weather", target)
runtime.call("_advance_facility_upkeep", target)
runtime.call("_advance_operations_time", target)
if bool(runtime.get_operations_report().get(&"runtime_failure_latched", false)):
break
@@ -283,17 +286,17 @@ func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
_expect(bool(enabled.get(&"ok", false)), "payroll fixture enables Operations after the pre-opening hire")
var full_advanced := _advance_operations_boundary_slice(
fixture, SimulationKernel.MINUTES_PER_DAY
fixture, SimulationKernel.MINUTES_PER_DAY - 360
)
var full: Dictionary = runtime.staff_service.get_payroll_settlement(&"payroll-day-00000001")
_expect(
full_advanced == SimulationKernel.MINUTES_PER_DAY
full_advanced == SimulationKernel.MINUTES_PER_DAY - 360
and _operations_slice_is_healthy(fixture)
and clock.absolute_minute == SimulationKernel.MINUTES_PER_DAY + 6 * 60
and clock.absolute_minute == SimulationKernel.MINUTES_PER_DAY
and int(full.get(&"new_wages_total", -1)) == daily_wage
and int(full.get(&"paid_total", -1)) == daily_wage
and int(full.get(&"unpaid_after_total", -1)) == 0,
"Day 1 payroll is paid in full and closes at exactly 06:00"
"Day 1 payroll is paid in full and closes at exactly 00:00"
)
var cash_now: int = runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT)
@@ -318,7 +321,7 @@ func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
_expect(
partial_advanced == SimulationKernel.MINUTES_PER_DAY
and _operations_slice_is_healthy(fixture)
and clock.absolute_minute == 2 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
and clock.absolute_minute == 2 * SimulationKernel.MINUTES_PER_DAY
and int(partial.get(&"new_wages_total", -1)) == daily_wage
and int(partial.get(&"paid_total", -1)) == 50
and int(partial.get(&"unpaid_after_total", -1)) == daily_wage - 50,
@@ -326,24 +329,21 @@ func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
)
var terminal_advanced := _advance_operations_boundary_slice(
fixture, 2 * SimulationKernel.MINUTES_PER_DAY
fixture, SimulationKernel.MINUTES_PER_DAY
)
var coordinator: OperationsCoordinator = runtime.operations_coordinator
var last_three_negative := true
for day in [2, 3, 4]:
for day in [1, 2, 3]:
var summary: Dictionary = coordinator.get_day_summary(day)
last_three_negative = last_three_negative \
and int(summary.get(&"net_profit", 0)) < 0 \
and int((summary.get(&"settlement", {}) as Dictionary).get(
&"arrears_total", 0
)) > 0
and int(summary.get(&"net_profit", 0)) < 0
_expect(
terminal_advanced == 2 * SimulationKernel.MINUTES_PER_DAY
and clock.absolute_minute == 4 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
terminal_advanced == SimulationKernel.MINUTES_PER_DAY
and clock.absolute_minute == 3 * SimulationKernel.MINUTES_PER_DAY
and last_three_negative
and coordinator.get_consecutive_insolvency_count() == 3
and coordinator.get_terminal_state() == &"BANKRUPT",
"three consecutive negative closes with arrears enter BANKRUPT at the third boundary"
"three consecutive negative closes, including the fully paid first day, enter BANKRUPT at the third boundary"
)
var terminal_snapshot: Dictionary = runtime.create_snapshot()
var terminal_digest := SimulationDigest.compute(terminal_snapshot)
@@ -353,7 +353,7 @@ func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
)
clock.advance_minutes(180)
_expect(
clock.absolute_minute == 4 * SimulationKernel.MINUTES_PER_DAY + 6 * 60
clock.absolute_minute == 3 * SimulationKernel.MINUTES_PER_DAY
and SimulationDigest.compute(runtime.create_snapshot()) == terminal_digest,
"BANKRUPT is terminal: later time requests are exact no-ops"
)
@@ -362,6 +362,9 @@ func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
"BANKRUPT cannot be acknowledged away"
)
var blocked_command: CommandResult = runtime.submit_command(&"change_resources", {&"deltas": {&"gold": 500}})
_expect(not blocked_command.is_success() and SimulationDigest.compute(runtime.create_snapshot()) == terminal_digest, "terminal rejects new commands without changing funds, command IDs, or history")
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
@@ -20,6 +20,7 @@ func _run() -> void:
_test_after_mutation_notifications_are_atomic()
_test_deferred_failure_backlog_and_exact_retry()
_test_daily_payroll_financials_and_crosslink_tamper()
_test_legacy_financial_calendar()
_cleanup()
if _failures == 0:
print("PASS: %d checks" % _checks)
@@ -57,7 +58,7 @@ func _test_disabled_compatibility_and_enable() -> void:
_expect(not disabled[&"enabled"], "Operations remain disabled for existing Runtime tests")
var disabled_snapshot: Dictionary = _runtime.create_snapshot()
var disabled_runtime: Dictionary = disabled_snapshot[&"systems"][&"runtime"]
_expect(int(disabled_runtime[&"runtime_schema_version"]) == 9, "Runtime snapshot advances to schema v9")
_expect(int(disabled_runtime[&"runtime_schema_version"]) == 13, "Runtime snapshot advances to schema v13")
_expect(
disabled_runtime[&"operations"] == {
&"wrapper_version": 1,
@@ -226,7 +227,7 @@ func _test_deferred_failure_backlog_and_exact_retry() -> void:
&"phase": phase,
}
)
_clock.set_speed(4)
_clock.set_speed(3)
var processed: int = _clock.process_real_delta(5.0)
var failed_report: Dictionary = _runtime.get_operations_report()
_expect(
@@ -297,9 +298,9 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
var settlement: Dictionary = summary.get(&"settlement", {})
_expect(
_clock.absolute_minute == 1_800 \
and int(summary.get(&"closed_at", -1)) == 1_800 \
and int(summary.get(&"closed_at", -1)) == 1_440 \
and int(summary[&"category_totals"][&"wages"]) == 120,
"06:00 closes Day 1 once and accrues only the new daily wage"
"00:00 closes Day 1 once and accrues only the new daily wage"
)
_expect(
int(settlement.get(&"paid_total", -1)) == 120 \
@@ -314,7 +315,7 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
_expect(
wage_entries.size() == 1 \
and wage_entries[0].category == &"WAGE" \
and wage_entries[0].absolute_minute == 1_800 \
and wage_entries[0].absolute_minute == 1_440 \
and wage_entries[0].amount == 120,
"paid payroll posts one canonical WAGE ledger line at the closing boundary"
)
@@ -363,6 +364,7 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
downgraded[&"systems"][&"runtime"].erase(&"kitchen_production")
downgraded[&"systems"][&"runtime"].erase(&"elevators")
downgraded[&"systems"][&"runtime"].erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(downgraded[&"systems"][&"runtime"])
for infrastructure_key: StringName in [&"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure"]:
downgraded[&"systems"][&"runtime"].erase(infrastructure_key)
downgraded[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
@@ -376,6 +378,35 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
)
func _test_legacy_financial_calendar() -> void:
_reset_runtime(811_005)
_expect(_runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": &"staff_akane", &"current_cell": Vector3i.ZERO}).is_success(), "legacy Runtime fixture hires a real wage-bearing employee")
_expect(bool(_runtime.enable_production_operations().get(&"ok", false)), "legacy Runtime fixture enables operations")
# Reproduce the old calendar and its zero-arrears counter. Payroll and ledger
# entries stay untouched; only the new loss metadata is converted to v3 semantics.
_runtime.operations_coordinator._legacy_calendar_last_day = 1
_clock.advance_minutes(1440)
var snapshot: Dictionary = _runtime.create_snapshot()
var old: Dictionary = snapshot[&"systems"][&"runtime"][&"operations"][&"coordinator"]
old[&"schema_version"] = 3
old.erase(&"legacy_calendar_last_day")
old.erase(&"legacy_history_last_day")
old[&"consecutive_insolvency_count"] = 0
for row: Dictionary in old[&"day_summaries"]:
row[&"consecutive_insolvency_count"] = 0
old[&"alerts"] = (old[&"alerts"] as Array).filter(func(row: Dictionary) -> bool: return String(row.get(&"code", "")) != "LOSS_STREAK")
_expect(int(old[&"day_summaries"][0][&"settlement"][&"paid_total"]) == 120, "legacy fixture contains an actual paid payroll at 06:00")
_expect(_runtime.restore_snapshot(snapshot), "Runtime restores v3 06:00 payroll and ledger crosslinks")
_clock.advance_minutes(1440)
_expect(_runtime.operations_coordinator.get_day_summary(2)[&"closed_at"] == 3240, "legacy open day completes at 06:00 once")
var transition: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(transition), "Runtime restores the transition checkpoint")
_clock.advance_minutes(1080)
var summary: Dictionary = _runtime.operations_coordinator.get_day_summary(3)
_expect(summary[&"closed_at"] == 4320 and summary[&"period_start_minute"] == 3240, "Runtime transitions to midnight with a continuous 18-hour financial period")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "midnight transition preserves payroll and ledger crosslinks on load")
func _rejects_atomically(candidate: Dictionary) -> bool:
var before: Dictionary = _runtime.create_snapshot()
return not _runtime.restore_snapshot(candidate) \
+103
View File
@@ -0,0 +1,103 @@
extends SceneTree
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 clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
clock.reset(1, 6, 0)
state.reset()
expect(runtime.initialize(clock, state, 424242)[&"valid"], "initialize")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable")
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"standard_room")
var original_rate := definition.maintenance_gold
definition.maintenance_gold = 24
expect(runtime.facility_service.create_instance({&"instance_id": &"upkeep-room", &"definition_id": &"standard_room"}).is_success(), "completed room")
runtime.facility_service.set_operational_status(&"upkeep-room", {&"suspension_reason": &"DESTROYED"}, 360)
clock.advance_minutes(60)
expect(runtime.facility_upkeep.get_report()[&"pending_facility_units"].get("upkeep-room", 0) == 1440, "suspended room accrues one hour")
runtime.facility_service.unregister_instance(&"upkeep-room")
clock.advance_minutes(1019)
var before: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(before), "23:59 save restores incurred upkeep after demolition")
clock.advance_minutes(1)
var summary: Dictionary = runtime.operations_coordinator.get_day_summary(1)
expect(not summary.is_empty(), "midnight closes")
if not summary.is_empty():
expect(summary[&"category_totals"][&"maintenance"] == 1, "one hour charged, post demolition no accrual")
expect(summary[&"facility_profit_loss"].get(&"upkeep-room", {}).get(&"expenses", 0) == 1, "demolished facility expense retained")
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1, "expense ledger")
var saved: Dictionary = runtime.create_snapshot()
DirAccess.make_dir_recursive_absolute("res://artifacts/upkeep-2026-09-29")
FileAccess.open("res://artifacts/upkeep-2026-09-29/preclose-snapshot.txt", FileAccess.WRITE).store_string(var_to_str(before))
FileAccess.open("res://artifacts/upkeep-2026-09-29/closed-snapshot.txt", FileAccess.WRITE).store_string(var_to_str(saved))
expect(runtime.restore_snapshot(saved), "closed upkeep save roundtrip")
expect(runtime.create_snapshot() == saved, "closed snapshot exact")
var panel := OperatingLedgerPanel.new()
panel.setup(runtime, clock)
expect(panel.get_report()[&"upkeep_cash_out"] == 1, "UI shows midnight maintenance payment")
expect(panel.get_report()[&"upkeep_arrears"] == 0, "UI distinguishes unpaid balance")
panel.free()
var tampered := saved.duplicate(true)
tampered[&"systems"][&"runtime"][&"facility_upkeep"][&"closed_periods"][0][&"facility_units"]["upkeep-room"] += 1
expect(not runtime.restore_snapshot(tampered) and runtime.create_snapshot() == saved, "receipt cannot diverge from accrued units")
expect(runtime.restore_snapshot(before), "restore before unpaid close")
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
expect(runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", cash, 1439, &"CONSTRUCTION").is_success(), "spend cash before midnight")
clock.advance_minutes(1)
expect(runtime.upkeep_settlement.get_report()[&"arrears_total"] == 1, "insufficient funds retain full upkeep debt")
expect(runtime.operations_coordinator.get_arrears_total() == 1, "coordinator includes upkeep debt")
expect(runtime.operations_coordinator.get_day_summary(1)[&"category_totals"][&"maintenance"] == 1, "unpaid upkeep still reduces profit")
var unpaid: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(unpaid), "unpaid close saves and restores")
expect(runtime.create_snapshot() == unpaid, "unpaid close does not charge twice")
# A controlled construction refund funds next-day repayment. This is a ledger
# fixture, not evidence of natural sales or a normal-play balance result.
expect(runtime.economy_service.transfer(&"CONSTRUCTION_EXPENSE", &"PLAYER_CASH", 2, 1440, &"CONSTRUCTION_REFUND").is_success(), "refund provides repayment funds")
var funded: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(funded), "funded debt survives save restore")
clock.advance_minutes(1439)
expect(runtime.upkeep_settlement.get_arrears_total() == 1 and runtime.economy_service.get_balance(&"PLAYER_CASH") == 2, "debt awaits next midnight")
var repayment_before: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(repayment_before), "next 23:59 restores outstanding debt")
clock.advance_minutes(1)
expect(not runtime.get_operations_report()[&"runtime_failure_latched"], "next-day repayment completes without failure")
expect(runtime.upkeep_settlement.get_arrears_total() == 0 and runtime.operations_coordinator.get_arrears_total() == 0, "next midnight clears runtime and coordinator debt")
expect(runtime.economy_service.get_balance(&"PLAYER_CASH") == 1, "old debt paid exactly once")
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1, "repayment does not recognize expense twice")
var next_summary: Dictionary = runtime.operations_coordinator.get_day_summary(2)
expect(not next_summary.is_empty() and next_summary[&"category_totals"][&"maintenance"] == 0, "next-day profit excludes prior expense repayment")
var repaid: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(repaid) and runtime.create_snapshot() == repaid, "repaid snapshot roundtrip is exact")
# Failure after posting upkeep must roll back receipts, ledger, cash and payroll.
expect(runtime.restore_snapshot(before), "restore before failed close")
expect(runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", 1, 1439, &"UNKNOWN_TEST_CATEGORY").is_success(), "inject classification failure")
var ledger_before: Dictionary = runtime.economy_service.create_snapshot()
clock.advance_minutes(1)
expect(runtime.get_operations_report()[&"runtime_failure_latched"], "failed close pauses runtime")
expect(runtime.economy_service.create_snapshot() == ledger_before, "post-upkeep failure rolls ledger back exactly")
expect(runtime.upkeep_settlement.get_report()[&"receipts"].is_empty(), "failed close leaves no settlement receipt")
# Old saves start charging only from the restored minute.
expect(runtime.restore_snapshot(before), "recover canonical preclose state")
var legacy := before.duplicate(true)
var data: Dictionary = legacy[&"systems"][&"runtime"]
data[&"runtime_schema_version"] = 9
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(data)
expect(runtime.restore_snapshot(legacy), "genuine v9 migrates")
expect(runtime.facility_upkeep.create_snapshot()[&"started_at"] == 1439, "migration starts at save minute without retroactive charge")
clock.advance_minutes(1)
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 0, "legacy save never bills pre-migration ownership")
definition.maintenance_gold = original_rate
runtime.free()
clock.free()
state.free()
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
+1
View File
@@ -0,0 +1 @@
uid://bm2d2aq11wnev
@@ -0,0 +1,117 @@
extends SceneTree
## Boundary coverage for owned canonical unique employment reports.
## Current definitions supply values; synthetic identity/link cases are disclosed.
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("UNIQUE_CONTRACT_REPORT: " + label)
func row_for(staff_id: StringName) -> Dictionary:
for row: Dictionary in runtime.get_staff_management_report()[&"staff"]:
if row[&"staff_id"] == staff_id:
return row
return {}
func effect_for(staff_id: StringName) -> Dictionary:
var row := row_for(staff_id)
var contract: Dictionary = row.get(&"unique_contract", {})
var effects: Array = contract.get(&"passive_effects", [])
return effects[0] if effects.size() == 1 else {}
func run() -> void:
runtime = load("res://autoload/simulation_runtime.gd").new()
clock = load("res://autoload/game_clock.gd").new()
state = load("res://autoload/game_state.gd").new()
clock.reset(1, 6, 0)
state.reset()
expect(runtime.initialize(clock, state, 42602)[&"valid"], "canonical content initializes")
clock.set_paused(true)
expect(runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": &"heroine_anri"}).is_success(), "normal approved command joins canonical Anri")
expect(runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "normal paid candidate remains available")
var unassigned := row_for(&"heroine_anri")
var contract: Dictionary = unassigned.get(&"unique_contract", {})
expect(contract.get(&"canonical_id") == &"heroine_anri" and contract.get(&"free_employment") == true and contract.get(&"free_upkeep") == true and contract.get(&"permanent_removal_forbidden") == true, "report connects canonical free and protected employment")
expect(row_for(&"staff_akane").get(&"unique_contract", {}).is_empty(), "ordinary paid actor receives no unique contract")
expect(effect_for(&"heroine_anri").get(&"multiplier_bp") == 12000 and effect_for(&"heroine_anri").get(&"applied") == false and not effect_for(&"heroine_anri").has(&"floor_y"), "unassigned report preserves original effect but has no invented target")
var panel := StaffManagementPanel.new()
var unassigned_text: String = panel._unique_contract_text(unassigned)
expect(unassigned_text.contains("雇用・維持費無料") and unassigned_text.contains("解雇・売却不可") and unassigned_text.contains("未適用(配属先なし・施設不在)"), "UI presents valid unassigned contract without a false active floor")
expect(panel._unique_contract_text(row_for(&"staff_akane")).is_empty(), "ordinary UI does not infer unique identity from wages")
identity_boundaries()
for request: Array in [[&"reception", Vector3i(-6,0,0)], [&"warehouse", Vector3i(-6,0,1)], [&"premium_room", Vector3i(-6,2,0)], [&"warehouse", Vector3i(-6,2,1)], [&"service_stairs", Vector3i(-7,1,1)], [&"service_stairs", Vector3i(-7,2,1)]]:
var built: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": request[0], &"origin": request[1]})
expect(built.is_success(), "normal construction provides accessible upper-floor fixtures")
if not built.is_success():
finish(panel)
return
clock.advance_minutes(runtime.definition_registry.get_facility(request[0]).build_minutes)
var workplace: FacilityInstance
var room: FacilityInstance
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"warehouse" and facility.origin.y == 2:
workplace = facility
if facility.definition_id == &"premium_room":
room = facility
expect(workplace != null and room != null, "real upper workplace and opposite-layer room exist")
if workplace == null or room == null:
finish(panel)
return
expect(runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": &"heroine_anri", &"facility_id": workplace.instance_id, &"template_id": &"NIGHT"}).is_success(), "normal assignment uses existing path/role/capacity checks")
var effect := effect_for(&"heroine_anri")
expect(effect.get(&"applied") == true and effect.get(&"floor_y") == 2 and effect.get(&"assigned_facility_id") == workplace.instance_id and effect.get(&"scope") == &"ASSIGNED_FLOOR_ALL_LAYERS", "report exposes actual planned target floor across both layers")
expect(runtime.get_floor_price_multiplier(2) == effect[&"multiplier_bp"] and runtime.get_floor_price_multiplier(0) == 10000 and runtime.get_facility_business_price(room) == 264, "presentation matches existing real pricing without changing its rounding")
expect(panel._unique_contract_text(row_for(&"heroine_anri")).contains("対象:3F(表層・裏層の全施設)"), "UI converts saved target Y with the existing floor naming convention")
var worker: StaffRuntime = runtime.staff_service.get_staff(&"heroine_anri")
var old_state := worker.state
worker.state = StaffRuntime.State.OFF_DUTY # Disclosed read-only applicability boundary fixture.
expect(effect_for(&"heroine_anri").get(&"applied") == true and runtime.get_floor_price_multiplier(2) == 12000, "passive placement does not acquire an attendance condition")
worker.state = old_state
var old_operation := workplace.operation_state
workplace.operation_state = FacilityInstance.OperationState.SUSPENDED # Presentation-only boundary fixture.
expect(effect_for(&"heroine_anri").get(&"applied") == true and runtime.get_floor_price_multiplier(2) == 12000, "passive placement does not acquire a workplace-operation condition")
workplace.operation_state = old_operation
var before: Dictionary = runtime.create_snapshot()
var before_bytes: PackedByteArray = var_to_bytes(before)
var exposed := row_for(&"heroine_anri")
exposed[&"unique_contract"][&"free_upkeep"] = false
exposed[&"unique_contract"][&"passive_effects"][0][&"floor_y"] = -999
exposed[&"unique_contract"][&"passive_effects"].clear()
expect(effect_for(&"heroine_anri").get(&"floor_y") == 2 and row_for(&"heroine_anri")[&"unique_contract"][&"free_upkeep"] == true, "report callers cannot mutate future contract/effect presentation")
expect(runtime.create_snapshot() == before and var_to_bytes(runtime.create_snapshot()) == before_bytes, "report and text rendering leave every live field/event/RNG/ID/queue byte unchanged")
var missing_plan: Dictionary = runtime.staff_service._work_assignments[&"heroine_anri"].duplicate(true)
runtime.staff_service._work_assignments[&"heroine_anri"][&"facility_id"] = &"absent_contract_workplace" # Invalid-link observation fixture, never saved as valid data.
expect(effect_for(&"heroine_anri").get(&"applied") == false and not effect_for(&"heroine_anri").has(&"floor_y") and runtime.get_floor_price_multiplier(2) == 10000, "missing facility neither invents a target nor keeps an active bonus")
expect(panel._unique_contract_text(row_for(&"heroine_anri")).contains("未適用(配属先なし・施設不在)"), "missing target is actionable in the ordinary detail text")
runtime.staff_service._work_assignments[&"heroine_anri"] = missing_plan
expect(runtime.create_snapshot() == before and var_to_bytes(runtime.create_snapshot()) == before_bytes, "invalid-link fixture was fully restored before compatibility assertions")
var report_before: Dictionary = runtime.get_staff_management_report()
expect(runtime.restore_snapshot(before), "unchanged existing schema restores canonical hire and planned placement")
expect(runtime.create_snapshot() == before and var_to_bytes(runtime.create_snapshot()) == before_bytes and runtime.get_staff_management_report() == report_before, "normal restore reconstructs report from persisted existing fields without new save values")
expect(not runtime.staff_service.remove_staff(&"heroine_anri", &"SOLD").is_success() and not runtime.staff_service.retire_staff(&"heroine_anri").is_success() and runtime.create_snapshot() == before, "visible protection corresponds to existing atomic remove/retire rejection")
finish(panel)
func identity_boundaries() -> void:
for options: Array in [[&"heroine_anri", &"innkeeper", &"FREE_HIRE"], [&"heroine_anri", &"cleaner", &"UNIQUE"], [&"unregistered_unique_fixture", &"innkeeper", &"UNIQUE"], [&"paid_zero_wage_fixture", &"receptionist", &"FREE_HIRE"]]:
var staff := StaffRuntime.new().configure(options[0], options[1], {&"employment_kind": options[2], &"daily_wage": 0})
expect(runtime._staff_unique_contract_report(staff).is_empty(), "synthetic wrong identity/kind/role or zero salary is not promoted to canonical unique")
var rai := StaffRuntime.new().configure(&"rival_rai", &"influencer", {&"employment_kind": &"UNIQUE"})
var contract: Dictionary = runtime._staff_unique_contract_report(rai)
expect(contract.get(&"canonical_id") == &"rival_rai" and contract.get(&"permanent_removal_forbidden") == true and contract.get(&"passive_effects", []).is_empty(), "canonical Rai gets general protection but no invented Anri effect or normal joining")
func finish(panel: StaffManagementPanel) -> void:
panel.free()
runtime.free()
clock.free()
state.free()
print("STAFF_UNIQUE_CONTRACT_REPORT checks=%d failures=%d" % [checks, failures])
quit(failures)
@@ -0,0 +1 @@
uid://c1qit82sded3m
@@ -0,0 +1,288 @@
extends SceneTree
## Boundary coverage for trusted per-person character definitions.
## The synthetic worker borrows existing metadata solely to exercise validators.
## It is not Fine, a proposed profile, recruitment or an active Runtime effect.
const CatalogType := preload("res://scripts/data/mvp_content_catalog.gd")
const FIXTURE_ID: StringName = &"heroine_policy_cleaner_fixture"
class EntryProbe:
extends MvpContentCatalog
func inspect_entry(entry: Dictionary) -> Dictionary:
_reset()
_validate_entry(&"unique_characters", StringName(_value(entry, &"id", &"")), entry)
return get_report()
func inspect_links(entries: Array, staff_index: Dictionary = {}) -> Dictionary:
_reset()
var index: Dictionary = {}
var ids: Array[StringName] = []
for entry: Dictionary in entries:
var entry_id: StringName = StringName(_value(entry, &"id", &""))
var errors_before: int = _errors.size()
_validate_entry(&"unique_characters", entry_id, entry)
if _errors.size() == errors_before:
index[entry_id] = _normalized_entry(entry)
ids.append(entry_id)
ids.sort_custom(_name_less)
_entries_by_type[&"unique_characters"] = index
_entries_by_type[&"staff_candidates"] = staff_index.duplicate(true)
_ordered_ids_by_type[&"unique_characters"] = ids
_validate_cross_references()
return get_report()
func policy_copy(entry_id: StringName) -> Dictionary:
return _unique_validation_policy(entry_id)
class ExplicitWorkerPolicyProbe:
extends EntryProbe
func _unique_validation_policy(entry_id: StringName) -> Dictionary:
if entry_id == &"heroine_policy_cleaner_fixture":
return {
&"character_kind": &"heroine",
&"mechanical_effect_ids": [&"cleaning_work_rate_multiplier"],
}
return super._unique_validation_policy(entry_id)
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("run")
func run() -> void:
baseline_contract()
explicit_worker_boundary()
person_effect_isolation()
pair_boundary()
common_safety_boundary()
roster_atomicity_and_recovery()
if failures == 0:
print("PASS: UniqueCharacterValidationPolicy (%d checks)" % checks)
else:
push_error("FAIL: UniqueCharacterValidationPolicy (%d of %d checks failed)" % [failures, checks])
quit(failures)
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok:
failures += 1
push_error("UNIQUE_POLICY: " + label)
func codes(report: Dictionary) -> Array[StringName]:
var result: Array[StringName] = []
for issue: Dictionary in report[&"errors"]:
result.append(StringName(issue[&"code"]))
return result
func documents() -> Dictionary:
var result: Dictionary = {}
for content_type: StringName in CatalogType.DEFAULT_PATHS:
var file := FileAccess.open(CatalogType.DEFAULT_PATHS[content_type], FileAccess.READ)
if file == null:
expect(false, "canonical input readable: " + String(content_type))
return {}
result[content_type] = JSON.parse_string(file.get_as_text())
file.close()
return result
func unique_entry(entry_id: StringName) -> Dictionary:
for entry: Dictionary in documents()[&"unique_characters"]["entries"]:
if StringName(entry["id"]) == entry_id:
return entry.duplicate(true)
return {}
func worker_entry() -> Dictionary:
var entry: Dictionary = unique_entry(&"heroine_anri")
entry["id"] = String(FIXTURE_ID)
entry["display_name"] = "検証専用・架空の清掃担当"
entry["role_id"] = "cleaner"
entry["mechanical_effects"] = {"cleaning_work_rate_multiplier": 3}
entry.erase("counterpart_id")
return entry
func baseline_contract() -> void:
var catalog := CatalogType.new()
var report: Dictionary = catalog.rebuild()
expect(bool(report[&"valid"]) and int(report[&"error_count"]) == 0, "unchanged two-person JSON validates")
expect(catalog.get_unique_character_ids() == [&"heroine_anri", &"rival_rai"], "normal unique roster remains exactly Anri and Rai")
expect(catalog.get_recruitable_staff_ids().size() == 22, "21 paid candidates plus approved Anri only")
expect(catalog.get_recruitment_entry(&"rival_rai").is_empty(), "Rai is not silently made recruitable")
expect(catalog.get_recruitment_entry(&"heroine_fine").is_empty(), "Fine is not registered")
expect(catalog.get_recruitment_entry(FIXTURE_ID).is_empty(), "synthetic worker is not registered")
var anri: Dictionary = catalog.get_entry(&"unique_characters", &"heroine_anri")
var rai: Dictionary = catalog.get_entry(&"unique_characters", &"rival_rai")
expect(anri[&"mechanical_effects"] == {&"assigned_floor_revenue_multiplier_bp": 12000}, "Anri's existing approved revenue definition is preserved")
expect(rai[&"mechanical_effects"] == {&"base_shield_attack": 8, &"night_shield_attack_bonus": 4}, "Rai's existing bounded attack definition is preserved")
expect(anri[&"counterpart_id"] == &"rival_rai" and rai[&"counterpart_id"] == &"heroine_anri", "explicit current MVP pair is preserved")
var payload: Dictionary = catalog.staff_hire_payload(&"heroine_anri")
expect(payload[&"options"][&"employment_kind"] == &"UNIQUE" and payload[&"options"][&"daily_wage"] == 0, "existing Anri hire payload remains free UNIQUE")
var probe := EntryProbe.new()
var policy: Dictionary = probe.policy_copy(&"heroine_anri")
policy[&"mechanical_effect_ids"].clear()
policy[&"counterpart_id"] = &"fabricated"
var pristine: Dictionary = probe.policy_copy(&"heroine_anri")
expect(pristine[&"mechanical_effect_ids"] == [&"assigned_floor_revenue_multiplier_bp"] and pristine[&"counterpart_id"] == &"rival_rai", "returned policy is an owned nested copy")
expect(probe.policy_copy(FIXTURE_ID).is_empty() and probe.policy_copy(&"heroine_fine").is_empty(), "production policy cannot infer synthetic or Fine identities")
func explicit_worker_boundary() -> void:
var probe := ExplicitWorkerPolicyProbe.new()
var entry: Dictionary = worker_entry()
var report: Dictionary = probe.inspect_entry(entry)
expect(int(report[&"error_count"]) == 0, "explicit synthetic policy accepts cleaning 3 without revenue or counterpart")
expect(not entry.has("counterpart_id") and not entry["mechanical_effects"].has("assigned_floor_revenue_multiplier_bp"), "positive fixture has neither an invented partner nor Anri effect")
report = probe.inspect_links([entry])
expect(int(report[&"error_count"]) == 0, "unpaired synthetic worker has no orphan or reciprocal requirement")
var ordinary_probe := EntryProbe.new()
report = ordinary_probe.inspect_entry(entry)
expect(&"missing_unique_policy" in codes(report), "same data without a trusted ID policy rejects")
for raw_counterpart: Variant in [null, "", "rival_rai", FIXTURE_ID, 7, true]:
entry = worker_entry()
entry["counterpart_id"] = raw_counterpart
report = probe.inspect_entry(entry)
expect(&"unexpected_counterpart" in codes(report), "unpaired policy rejects present counterpart value %s" % str(raw_counterpart))
entry = worker_entry()
entry[&"counterpart_id"] = &""
expect(&"unexpected_counterpart" in codes(probe.inspect_entry(entry)), "StringName counterpart presence also rejects")
for raw_value: Variant in [3, 3.0]:
entry = worker_entry()
entry["mechanical_effects"]["cleaning_work_rate_multiplier"] = raw_value
expect(int(probe.inspect_entry(entry)[&"error_count"]) == 0, "integer-compatible exact 3 follows existing numeric contract")
for raw_value: Variant in [0, 1, 2, 4, -3, 1000001, "3", true, null, 3.5, NAN, INF]:
entry = worker_entry()
entry["mechanical_effects"]["cleaning_work_rate_multiplier"] = raw_value
expect(&"invalid_mechanical_effect" in codes(probe.inspect_entry(entry)), "non-exact cleaning multiplier rejects: %s" % str(raw_value))
entry = worker_entry()
entry["id"] = "heroine_unknown_worker"
expect(&"missing_unique_policy" in codes(probe.inspect_entry(entry)), "fixture override does not accept arbitrary other workers")
entry = worker_entry()
entry["unique_policy"] = {"mechanical_effect_ids": ["cleaning_work_rate_multiplier"]}
expect(&"unknown_field" in codes(probe.inspect_entry(entry)), "JSON cannot inject or widen trusted policy")
func person_effect_isolation() -> void:
var probe := ExplicitWorkerPolicyProbe.new()
for person_id: StringName in [&"heroine_anri", &"rival_rai"]:
var entry: Dictionary = unique_entry(person_id)
entry["mechanical_effects"]["cleaning_work_rate_multiplier"] = 3
expect(&"effect_kind_mismatch" in codes(probe.inspect_entry(entry)), "reserved cleaning validator does not grant effect to " + String(person_id))
entry = unique_entry(person_id)
entry["mechanical_effects"] = {"cleaning_work_rate_multiplier": 3}
var issues: Array[StringName] = codes(probe.inspect_entry(entry))
expect(&"missing_mechanical_effect" in issues and &"effect_kind_mismatch" in issues, "replacement cleaning effect cannot satisfy " + String(person_id))
var worker: Dictionary = worker_entry()
worker["mechanical_effects"]["assigned_floor_revenue_multiplier_bp"] = 12000
expect(&"effect_kind_mismatch" in codes(probe.inspect_entry(worker)), "worker cannot acquire Anri's revenue effect")
worker = worker_entry()
worker["mechanical_effects"] = {"assigned_floor_revenue_multiplier_bp": 12000}
var worker_codes: Array[StringName] = codes(probe.inspect_entry(worker))
expect(&"missing_mechanical_effect" in worker_codes and &"effect_kind_mismatch" in worker_codes, "heroine kind is not a fallback to Anri effect")
worker = worker_entry()
worker["mechanical_effects"]["base_shield_attack"] = 8
expect(&"effect_kind_mismatch" in codes(probe.inspect_entry(worker)), "worker cannot acquire Rai's attack")
worker = worker_entry()
worker["mechanical_effects"]["arbitrary_runtime_hook"] = 1
var hook_codes: Array[StringName] = codes(probe.inspect_entry(worker))
expect(&"invalid_mechanical_effect" in hook_codes and &"effect_kind_mismatch" in hook_codes, "unknown hook remains rejected by value and person boundaries")
worker = worker_entry()
worker["mechanical_effects"] = {}
expect(&"missing_mechanical_effect" in codes(probe.inspect_entry(worker)), "an explicit worker still requires its own effect")
var anri: Dictionary = unique_entry(&"heroine_anri")
anri["character_kind"] = "rival"
expect(&"character_policy_mismatch" in codes(probe.inspect_entry(anri)), "known person's kind cannot be forged")
for person_id: StringName in [&"heroine_anri", &"rival_rai"]:
var entry: Dictionary = unique_entry(person_id)
entry["mechanical_effects"] = {"arbitrary_runtime_hook": 1}
var issues: Array[StringName] = codes(probe.inspect_entry(entry))
expect(&"invalid_mechanical_effect" in issues and &"effect_kind_mismatch" in issues and &"missing_mechanical_effect" in issues, "existing arbitrary hook safety contract retained for " + String(person_id))
for raw_value: Variant in [2499, 30001, "12000", true, NAN, INF]:
anri = unique_entry(&"heroine_anri")
anri["mechanical_effects"]["assigned_floor_revenue_multiplier_bp"] = raw_value
expect(&"invalid_mechanical_effect" in codes(probe.inspect_entry(anri)), "existing Anri bounds and type rejection retained")
for raw_value: Variant in [-1, 101, "8", true, NAN, INF]:
var rai: Dictionary = unique_entry(&"rival_rai")
rai["mechanical_effects"]["base_shield_attack"] = raw_value
expect(&"invalid_mechanical_effect" in codes(probe.inspect_entry(rai)), "existing Rai bounds and type rejection retained")
func pair_boundary() -> void:
var probe := ExplicitWorkerPolicyProbe.new()
var anri: Dictionary = unique_entry(&"heroine_anri")
var rai: Dictionary = unique_entry(&"rival_rai")
expect(int(probe.inspect_links([anri, rai, worker_entry()])[&"error_count"]) == 0, "existing explicit pair and a declared unpaired worker coexist at link boundary")
for person_id: StringName in [&"heroine_anri", &"rival_rai"]:
var paired_entry: Dictionary = unique_entry(person_id)
for raw_counterpart: Variant in [null, "", String(person_id), 7, true]:
paired_entry = unique_entry(person_id)
paired_entry["counterpart_id"] = raw_counterpart
expect(&"invalid_counterpart" in codes(probe.inspect_entry(paired_entry)), "paired person retains invalid counterpart rejection: " + String(person_id))
paired_entry = unique_entry(person_id)
paired_entry.erase("counterpart_id")
expect(&"invalid_counterpart" in codes(probe.inspect_entry(paired_entry)), "paired person cannot omit explicit partner: " + String(person_id))
anri = unique_entry(&"heroine_anri")
anri["counterpart_id"] = "missing_actor"
expect(&"missing_counterpart" in codes(probe.inspect_links([anri, rai])), "legacy orphan-reference error remains observable")
anri = unique_entry(&"heroine_anri")
anri["counterpart_id"] = String(FIXTURE_ID)
var issues: Array[StringName] = codes(probe.inspect_links([anri, rai, worker_entry()]))
expect(&"counterpart_policy_mismatch" in issues, "Anri cannot redirect to another declared worker")
expect(&"counterpart_not_reciprocal" in issues and &"counterpart_kind_conflict" in issues, "redirected relation still fails reciprocity and kind boundaries")
issues = codes(probe.inspect_links([worker_entry()], {FIXTURE_ID: {}}))
expect(&"runtime_actor_id_collision" in issues, "unpaired workers still participate in Runtime actor collision validation")
func common_safety_boundary() -> void:
var probe := ExplicitWorkerPolicyProbe.new()
for raw_verified: Variant in [false, "true", 1, null]:
var entry: Dictionary = worker_entry()
entry["adult_verified"] = raw_verified
expect(&"adult_verification_required" in codes(probe.inspect_entry(entry)), "declared policy cannot bypass explicit adult bool")
for raw_age: Variant in [17, 10001, "25", true, NAN, INF]:
var entry: Dictionary = worker_entry()
entry["age"] = raw_age
expect(&"adult_age_required" in codes(probe.inspect_entry(entry)), "declared policy cannot bypass existing age type/bounds")
for trait_id: String in ["unique", "unsellable"]:
var entry: Dictionary = worker_entry()
entry["traits"].erase(trait_id)
expect(&"unique_safety_traits_required" in codes(probe.inspect_entry(entry)), "declared worker still requires " + trait_id)
var entry: Dictionary = worker_entry()
entry["role_id"] = "maid"
expect(&"unknown_role" in codes(probe.inspect_entry(entry)), "policy cannot invent an unregistered role")
entry = worker_entry()
entry["skills"]["operation"] = 101
expect(&"map_value_out_of_range" in codes(probe.inspect_entry(entry)), "existing skill ceiling remains enforced")
entry = worker_entry()
entry["skills"].erase("operation")
expect(&"missing_map_key" in codes(probe.inspect_entry(entry)), "all canonical skills still required")
entry = worker_entry()
entry["mechanical_effects"][42] = 3
expect(&"canonical_key_collision" in codes(probe.inspect_entry(entry)), "non-text effect key cannot be coerced into a trusted effect ID")
entry = worker_entry()
entry["preferred_shift"] = "invalid_shift"
expect(&"unknown_shift" in codes(probe.inspect_entry(entry)), "known policy still requires registered shift")
entry = worker_entry()
entry["display_name"] = ""
expect(&"missing_display_name" in codes(probe.inspect_entry(entry)), "known policy still requires safe visible name")
func roster_atomicity_and_recovery() -> void:
var catalog := ExplicitWorkerPolicyProbe.new()
var baseline: Dictionary = documents()
var report: Dictionary = catalog.rebuild_from_documents(baseline)
expect(bool(report[&"valid"]), "fixture policy override does not alter current full baseline")
var generation: int = catalog.get_manifest_generation()
var changed: Dictionary = documents()
changed[&"unique_characters"]["entries"].append(worker_entry())
report = catalog.rebuild_from_documents(changed)
var issues: Array[StringName] = codes(report)
expect(not bool(report[&"valid"]) and &"mvp_count_mismatch" in issues and &"mvp_id_set_mismatch" in issues, "even explicitly declared third actor cannot enter the unchanged full roster")
expect(catalog.get_unique_character_ids().is_empty() and catalog.get_staff_candidate_ids().is_empty() and catalog.build_all_guest_definitions().is_empty(), "failed roster rebuild exposes no partial content")
expect(catalog.get_recruitable_staff_ids().is_empty() and catalog.staff_hire_payload(&"heroine_anri").is_empty(), "failed catalog cannot generate recruitment payloads")
expect(catalog.get_manifest_generation() == generation + 1, "failed rebuild invalidates transient generation exactly once")
report = catalog.rebuild_from_documents(baseline)
expect(bool(report[&"valid"]) and catalog.get_unique_character_ids() == [&"heroine_anri", &"rival_rai"], "exact baseline recovers without retaining the worker")
expect(catalog.get_manifest_generation() == generation + 2, "recovery invalidates generation exactly once")
changed = documents()
changed[&"unique_characters"]["entries"][0]["id"] = "heroine_unknown"
report = catalog.rebuild_from_documents(changed)
expect(not bool(report[&"valid"]) and &"missing_unique_policy" in codes(report), "same-count unknown identity is rejected before normal exposure")
expect(catalog.get_unique_character_ids().is_empty() and catalog.get_recruitable_staff_ids().is_empty(), "unknown person leaves no accepted or recruitable partial content")
changed = documents()
changed[&"unique_characters"]["entries"][0]["counterpart_id"] = "missing_actor"
var first: Dictionary = catalog.rebuild_from_documents(changed)
var second: Dictionary = catalog.rebuild_from_documents(changed)
expect(first[&"errors"] == second[&"errors"] and &"missing_counterpart" in codes(second), "original deterministic orphan error contract retained")
@@ -0,0 +1 @@
uid://befg4kqgqt1b2
@@ -0,0 +1,46 @@
extends SceneTree
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 clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
clock.reset(1, 6, 0)
state.reset()
expect(runtime.initialize(clock, state, 90416)[&"valid"], "initialize")
clock.set_paused(true)
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"guest_corridor")
var old_rate := definition.maintenance_gold
definition.maintenance_gold = 1440
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)}).is_success(), "public construction starts")
var facility: Dictionary = {}
for minute in 100:
clock.advance_minutes(1)
var instances: Array = runtime.construction_service.get_facilities()
if not instances.is_empty():
facility = instances[0]
break
expect(runtime.facility_upkeep.get_report()[&"pending_facility_units"].is_empty(), "construction time is not billable")
expect(not facility.is_empty(), "construction completes")
if not facility.is_empty():
expect(runtime.facility_upkeep.get_report()[&"pending_facility_units"].is_empty(), "completion instant adds no earlier expense")
clock.advance_minutes(3)
expect(runtime.facility_upkeep.get_report()[&"pending_facility_units"].get(String(facility.instance_id), 0) == 4320, "owned infrastructure accrues exactly three minutes")
var mid: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(mid) and runtime.create_snapshot() == mid, "real construction ownership saves exactly")
expect(runtime.submit_command(&"demolish_facility", {&"instance_id": facility.instance_id}).is_success(), "public demolition")
clock.advance_minutes(3)
expect(runtime.facility_upkeep.get_report()[&"pending_facility_units"].get(String(facility.instance_id), 0) == 4320, "demolition stops further accrual")
definition.maintenance_gold = old_rate
runtime.free()
clock.free()
state.free()
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
@@ -0,0 +1 @@
uid://dgkvfdou0w0y7
+79
View File
@@ -0,0 +1,79 @@
extends SceneTree
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("_run")
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok:
failures += 1
push_error(message)
func _run() -> void:
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()
expect(runtime.initialize(clock, state, 424242)[&"valid"], "initialize")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable")
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"guest_corridor")
var old_rate := definition.maintenance_gold
definition.maintenance_gold = 1440
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)}).is_success(), "build completed asset")
runtime.operations_coordinator._legacy_calendar_last_day = 1
clock.advance_minutes(1079)
var legacy: Dictionary = runtime.create_snapshot()
var data: Dictionary = legacy[&"systems"][&"runtime"]
data[&"runtime_schema_version"] = 9
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(data)
data[&"operations"][&"coordinator"][&"schema_version"] = 3
data[&"operations"][&"coordinator"].erase(&"legacy_calendar_last_day")
data[&"operations"][&"coordinator"].erase(&"legacy_history_last_day")
expect(runtime.restore_snapshot(legacy), "genuine v9/v3 calendar migrates before midnight")
var ledger_before: Dictionary = runtime.economy_service.create_snapshot()
runtime.set_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", func(_payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
return {&"ok": phase != runtime.OPERATIONS_TEST_AFTER_FIRST_MUTATION_PHASE})
clock.advance_minutes(1)
expect(runtime.get_operations_report()[&"runtime_failure_latched"] and clock.absolute_minute == 1440, "midnight failure pauses before legacy financial close")
expect(runtime.economy_service.create_snapshot() == ledger_before and runtime.upkeep_settlement.get_settled_period_count() == 0, "midnight rollback restores cash ledger and receipt atomically")
var failed: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(failed), "failed midnight checkpoint restores")
runtime.set_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", Callable())
expect(runtime.acknowledge_operations_pause()[&"ok"], "same midnight retries through existing acknowledgement")
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1 and runtime.upkeep_settlement.get_settled_period_count() == 1, "only post-migration minute settles at midnight")
expect(runtime.operations_coordinator.get_day_summaries().is_empty(), "legacy payroll and financial day are not rewritten")
var midnight: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(midnight) and runtime.create_snapshot() == midnight, "midnight interim payment roundtrips exactly")
clock.advance_minutes(360)
var summary: Dictionary = runtime.operations_coordinator.get_day_summary(1)
expect(not summary.is_empty() and summary[&"closed_at"] == 1800, "legacy financial close remains at 06:00")
if not summary.is_empty():
expect(summary[&"category_totals"][&"maintenance"] == 1 and runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1, "06:00 reports midnight expense without a second charge")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "06:00 history and earlier midnight receipt reconcile on load")
# A later failure must retain the already committed midnight payment exactly once.
expect(runtime.restore_snapshot(legacy), "restore before later-boundary failure")
runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", func(_payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
return {&"ok": phase != runtime.OPERATIONS_TEST_PREPARE_PHASE})
clock.advance_minutes(1)
expect(runtime.get_operations_report()[&"runtime_failure_latched"] and runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1, "later failure preserves committed midnight payment")
var after_payment_failure: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(after_payment_failure), "save distinguishes committed payment from pending later boundary")
runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", Callable())
expect(runtime.acknowledge_operations_pause()[&"ok"] and runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1 and runtime.upkeep_settlement.get_settled_period_count() == 1, "retry never duplicates earlier midnight payment")
# New interim debt blocks spending immediately, without rewriting old summaries.
expect(runtime.restore_snapshot(legacy), "restore legacy insufficient-cash branch")
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
expect(runtime.economy_service.transfer(&"PLAYER_CASH", &"CONSTRUCTION_EXPENSE", cash, 1439, &"CONSTRUCTION").is_success(), "deplete cash")
clock.advance_minutes(1)
expect(runtime.get_operations_report()[&"arrears_total"] == 1 and runtime.operations_coordinator.get_arrears_total() == 0, "live debt is visible while closed financial history stays unchanged")
clock.set_paused(true)
var blocked: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(5, 1, 0)})
expect(not blocked.is_success() and blocked.details.get(&"reason") == &"UNPAID_EXPENSES", "interim upkeep debt blocks new investment at midnight")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "interim debt saves and restores without a synthetic daily summary")
definition.maintenance_gold = old_rate
runtime.free()
clock.free()
state.free()
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
@@ -0,0 +1 @@
uid://iclut17j0oeq
+50
View File
@@ -0,0 +1,50 @@
extends SceneTree
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("_run")
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok:
failures += 1
push_error(message)
func _run() -> void:
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()
expect(runtime.initialize(clock, state, 424242)[&"valid"], "initialize")
clock.set_paused(true)
expect(runtime.enable_production_operations()[&"ok"], "enable production operations")
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"guest_corridor")
var old_rate := definition.maintenance_gold
definition.maintenance_gold = 1440
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)}).is_success(), "normal construction")
clock.advance_minutes(10)
expect(runtime.construction_service.get_facilities().size() == 1, "owned infrastructure completed")
for day in range(1, 3):
clock.advance_minutes(day * 1440 - clock.absolute_minute)
var summary: Dictionary = runtime.operations_coordinator.get_day_summary(day)
expect(not summary.is_empty(), "calendar day %d closes" % day)
if not summary.is_empty():
var expense := 1070 if day == 1 else 1440
expect(summary[&"category_totals"][&"maintenance"] == expense and summary[&"net_profit"] == -expense, "only ownership upkeep causes day %d loss" % day)
expect(summary[&"arrears_total"] == 0 and runtime.operations_coordinator.get_consecutive_insolvency_count() == day, "fully paid loss still advances consecutive losses")
print("UPKEEP_LOSS_PROGRESS day=", day)
clock.advance_minutes(1439)
var before: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(before), "restore one minute before third maintenance loss")
clock.advance_minutes(1)
expect(clock.absolute_minute == 4320 and clock.paused and runtime.operations_coordinator.is_terminal(), "third paid maintenance loss ends game at midnight")
expect(runtime.economy_service.get_balance(&"PLAYER_CASH") == 1030, "cash reconciles construction and three prorated bills")
var final: Dictionary = runtime.create_snapshot()
expect(runtime.restore_snapshot(final) and runtime.create_snapshot() == final, "terminal save preserves expenses and outcome exactly")
clock.advance_minutes(60)
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 3950 and runtime.upkeep_settlement.get_settled_period_count() == 3, "terminal clock never collects a fourth bill")
definition.maintenance_gold = old_rate
runtime.free()
clock.free()
state.free()
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
+1
View File
@@ -0,0 +1 @@
uid://cbkk7j2b5t15t
@@ -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
+7 -7
View File
@@ -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
+39
View File
@@ -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
+24
View File
@@ -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
+22 -3
View File
@@ -9,6 +9,7 @@ func _initialize() -> void:
func _run() -> void:
print("The Tree: automated checks")
_test_game_clock()
_test_original_clock_scale()
_test_game_state()
_test_facility_data()
load("res://tests/s0/test_kernel_and_save.gd").run(Callable(self, "_expect"))
@@ -25,14 +26,32 @@ func _test_game_clock() -> void:
clock.advance_minutes(4 * 60)
_expect(clock.get_hour() == 10, "clock advances to 10:00")
_expect(clock.get_phase() == clock.Phase.DAY, "10:00 is day phase")
clock.set_speed(4)
_expect(clock.speed == 4, "clock accepts the specified x4 speed")
clock.set_speed(3)
_expect(clock.speed == 4, "clock rejects unsupported speed values")
_expect(clock.speed == 3, "clock accepts the original x3 speed")
clock.set_speed(4)
_expect(clock.speed == 3, "clock rejects unsupported x4")
_expect(clock.REAL_SECONDS_PER_GAME_HOUR == 10.0, "original scale is ten seconds per game hour")
clock.advance_minutes(14 * 60)
_expect(clock.day == 2 and clock.get_hour() == 0, "midnight starts the next day")
clock.free()
func _test_original_clock_scale() -> void:
var clock := SimulationKernel.new()
for multiplier: int in [1, 2, 3]:
clock.reset()
clock.set_speed(multiplier)
for frame in 600:
clock.process_real_delta(1.0 / 60.0)
_expect(clock.absolute_minute == 360 + 60 * multiplier, "ten real seconds produce one hour at x%d" % multiplier)
clock.set_paused(true)
var minute := clock.absolute_minute
clock.process_real_delta(10.0)
_expect(clock.absolute_minute == minute, "pause preserves game time")
var legacy := clock.clock_snapshot()
legacy[&"speed"] = 4
_expect(clock.restore_clock(legacy) and clock.speed == 3, "legacy x4 resumes at x3")
clock.free()
func _test_game_state() -> void:
var state = load("res://autoload/game_state.gd").new()
state.reset()
+171
View File
@@ -0,0 +1,171 @@
extends SceneTree
# Actual 53-key recurrence plus text edge cases. No environment variables required.
# Full Runtime state/event/suffix equivalence remains a separate required gate.
const CommandType = preload("res://scripts/core/command.gd")
const QueueType = preload("res://scripts/core/command_queue.gd")
const ResultType = preload("res://scripts/core/command_result.gd")
const OBSERVED_KEYS: Array[String] = [
"guest.auto_queue.guest-auto-00000023.692",
"pixel_view.command.0044",
"pixel_view.command.0043",
"pixel_view.command.0042",
"pixel_view.command.0041",
"pixel_view.command.0013",
"pixel_view.command.0012",
"pixel_view.command.0011",
"pixel_view.command.0010",
"pixel_view.command.0009",
"pixel_view.command.0008",
"pixel_view.command.0004",
"pixel_view.command.0006",
"pixel_view.command.0007",
"pixel_view.command.0040",
"pixel_view.command.0039",
"pixel_view.command.0037",
"pixel_view.command.0036",
"pixel_view.command.0035",
"pixel_view.command.0034",
"pixel_view.command.0033",
"pixel_view.command.0029",
"pixel_view.command.0028",
"pixel_view.command.0027",
"pixel_view.command.0025",
"pixel_view.command.0024",
"pixel_view.command.0023",
"pixel_view.command.0022",
"pixel_view.command.0021",
"pixel_view.command.0020",
"pixel_view.command.0019",
"pixel_view.command.0017",
"pixel_view.command.0018",
"pixel_view.command.0016",
"pixel_view.command.0014",
"pixel_view.command.0015",
"pixel_view.command.0026",
"guest.auto_queue.guest-auto-00000024.692",
"guest.auto_queue.guest-auto-00000019.697",
"pixel_view.command.0005",
"guest.auto_queue.guest-auto-00000021.662",
"guest.auto_queue.guest-auto-00000026.722",
"guest.auto_queue.guest-auto-00000022.726",
"guest.auto_queue.guest-auto-00000022.662",
"pixel_view.command.0032",
"pixel_view.command.0031",
"pixel_view.command.0030",
"pixel_view.command.0001",
"pixel_view.command.0038",
"pixel_view.command.0002",
"guest.auto_queue.guest-auto-00000019.632",
"guest.auto_queue.guest-auto-00000020.632",
"pixel_view.command.0003",
]
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("Command Queue snapshot key-order regression")
var input: Array[String] = OBSERVED_KEYS.duplicate()
input.append_array(["A", "a", "a10", "a2", "alpha", "Alpha", "あ", "🌳"])
var sorted_text: Array[String] = input.duplicate()
sorted_text.sort()
var reference := PackedByteArray()
var reference_final := PackedByteArray()
for iteration: int in range(2):
var insertion: Array[String] = input.duplicate()
if iteration == 1:
insertion.reverse()
var queue := QueueType.new()
for key: String in insertion:
var identity := sorted_text.find(key) + 1
_expect(queue.enqueue(_command(identity, identity % 3, identity % 4, key)).is_success(), "accept unique actual/text fixture key")
var initially_ready := queue.drain_ready(0)
for index: int in range(initially_ready.size()):
if index % 2 == 0:
var command: SimulationCommand = initially_ready[index]
_expect(queue.complete(command.command_id, _result(command.command_id)).is_success(), "retain processed fixture result")
var snapshot := queue.create_snapshot()
var snapshot_bytes := var_to_bytes(snapshot)
_expect(not snapshot["pending"].is_empty() and not snapshot["in_flight"].is_empty() and not snapshot["processed"].is_empty(),
"fixture simultaneously covers pending, in-flight and retained results")
_assert_text_snapshot(snapshot, sorted_text)
if iteration == 0:
reference = snapshot_bytes
_expect(snapshot_bytes == reference, "complete typed snapshot ignores keyed command insertion order")
_expect(var_to_bytes(queue.create_snapshot()) == snapshot_bytes, "snapshot query is stable and read-only")
var reversed_source := snapshot.duplicate(true)
var reverse_keys: Array[String] = sorted_text.duplicate()
reverse_keys.reverse()
var raw_map: Dictionary = {}
for index: int in range(reverse_keys.size()):
var key: String = reverse_keys[index]
var raw_key: Variant = StringName(key) if index % 2 == 0 else key
raw_map[raw_key] = snapshot["idempotency"][key]
reversed_source["idempotency"] = raw_map
var restored := QueueType.new()
_expect(restored.restore_snapshot(reversed_source), "valid reordered mixed String/StringName saved map restores")
var restored_snapshot := restored.create_snapshot()
_assert_text_snapshot(restored_snapshot, sorted_text)
_expect(var_to_bytes(restored_snapshot) == snapshot_bytes, "all saved fields and map output bytes agree after restore")
for key: String in sorted_text:
_expect(queue.has_idempotency_key(StringName(key)) and restored.has_idempotency_key(StringName(key)), "every actual/text key remains available")
var before := queue.get_result_by_idempotency_key(StringName(key))
var after := restored.get_result_by_idempotency_key(StringName(key))
if before == null:
_expect(after == null, "unprocessed key does not invent a result")
else:
_expect(after != null and var_to_bytes(before.to_dictionary()) == var_to_bytes(after.to_dictionary()), "retained replay result agrees completely")
var duplicate := restored.enqueue(_command(10000 + sorted_text.find(key), 10, 0, key))
_expect(duplicate.code == ResultType.Code.DUPLICATE_IDEMPOTENCY_KEY and int(duplicate.details.get("original_command_id", 0)) == sorted_text.find(key) + 1,
"restored mapping rejects duplicates with the original command ID")
_expect(var_to_bytes(restored.create_snapshot()) == snapshot_bytes, "rejected duplicate lookups preserve every saved field")
var owned := queue.create_snapshot()
owned["idempotency"].clear()
owned["accepted"].clear()
_expect(var_to_bytes(queue.create_snapshot()) == snapshot_bytes, "returned containers cannot corrupt accepted/idempotency state")
var original_drained := _finish(queue, snapshot)
var restored_drained := _finish(restored, restored_snapshot)
_expect(var_to_bytes(original_drained) == var_to_bytes(restored_drained), "restored queue executes every pending command in the same tick/priority/ID order")
var final_bytes := var_to_bytes(queue.create_snapshot())
_expect(final_bytes == var_to_bytes(restored.create_snapshot()), "all final retained result/accepted/idempotency bytes agree")
if iteration == 0:
reference_final = final_bytes
_expect(final_bytes == reference_final, "both insertion orders preserve complete processed history")
print("Command Queue snapshot key-order regression: %d checks, %d failures" % [checks, failures])
quit(0 if failures == 0 else 1)
func _assert_text_snapshot(snapshot: Dictionary, sorted_text: Array[String]) -> void:
var converted: Array[String] = []
for raw_key: Variant in snapshot["idempotency"]:
_expect(typeof(raw_key) == TYPE_STRING, "saved idempotency key remains String")
_expect(typeof(snapshot["idempotency"][raw_key]) == TYPE_INT and int(snapshot["idempotency"][raw_key]) == sorted_text.find(String(raw_key)) + 1,
"saved idempotency value retains int type and original command ID")
converted.append(String(raw_key))
_expect(var_to_bytes(converted) == var_to_bytes(sorted_text), "saved key text matches ordinary String lexical order")
_expect(int(snapshot["version"]) == QueueType.SNAPSHOT_VERSION, "snapshot version remains unchanged")
func _command(identity: int, tick: int, priority: int, text: String) -> SimulationCommand:
return CommandType.new(identity, 0, tick, priority, &"Test", &"actor", &"target",
{"original_key": text, "nested": {"fixture": &"preserve", "value": identity}}, StringName(text))
func _result(identity: int) -> CommandResult:
return ResultType.succeeded(identity, {"number": identity, "nested": {"fixture": &"preserve"}})
func _finish(queue: CommandQueue, snapshot: Dictionary) -> Array[Dictionary]:
for saved: Dictionary in snapshot["in_flight"]:
var identity := int(saved["command_id"])
_expect(queue.complete(identity, _result(identity)).is_success(), "complete saved in-flight command with unchanged result")
var drained: Array[Dictionary] = []
for command: SimulationCommand in queue.drain_ready(10):
drained.append(command.to_dictionary())
_expect(queue.complete(command.command_id, _result(command.command_id)).is_success(), "complete ready command exactly once")
return drained
func _expect(condition: bool, label: String) -> void:
checks += 1
if not condition:
failures += 1
push_error(label)
@@ -0,0 +1 @@
uid://b5tv71yuw2fg4
+12
View File
@@ -13,6 +13,7 @@ func _initialize() -> void:
func _run() -> void:
_test_catalog_references()
_test_normal_catalog_lookup()
_test_expansion_catalog()
_test_default_registry()
_test_detailed_definition_schema()
@@ -83,6 +84,16 @@ func _test_catalog_references() -> void:
_expect(FacilityCatalogScript.by_id(&"not_a_facility") == null, "catalog by_id rejects an unknown reference")
func _test_normal_catalog_lookup() -> void:
# Every normal-build registration must also resolve through the public catalog.
var paths: Array[String] = FacilityCatalogScript.FACILITY_PATHS + FacilityCatalogScript.INFRASTRUCTURE_FACILITY_PATHS + FacilityCatalogScript.RESCUE_FACILITY_PATHS + FacilityCatalogScript.ORIGINAL_FACILITY_PATHS
for path: String in paths:
var registered := load(path) as FacilityDefinition
_expect(registered != null, "normal catalog resource loads: %s" % path)
if registered != null:
_expect(FacilityCatalogScript.by_id(registered.id) == registered, "normal registration resolves to identical definition: %s" % registered.id)
func _test_detailed_definition_schema() -> void:
var registry = DefinitionRegistryScript.new()
registry.rebuild()
@@ -204,6 +215,7 @@ func _test_queries() -> void:
&"service_elevator",
&"staff_door",
&"staff_waiting_room",
&"standard_room",
&"tavern",
&"warehouse",
]
+23
View File
@@ -8,6 +8,7 @@ func _initialize() -> void:
func _run() -> void:
print("S0: domain events")
_test_snapshot_id_cache()
_test_single_publish_and_snapshot()
_test_atomic_batch_and_reentrant_delivery()
_test_batch_rejection_is_atomic()
@@ -119,3 +120,25 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_snapshot_id_cache() -> void:
var stream := DomainEventStream.new()
var ids: Array[String] = ["z", "a", "A", "a10", "a2", String.chr(0x1f333), String.chr(0x3042)]
_expect(stream.restore_snapshot({"version":1,"published_ids":ids,"recent_events":[]}), "restore unsorted history fixture")
var expected := ids.duplicate()
expected.sort_custom(func(a: String,b: String):return a < b)
var first := stream.create_snapshot()
_expect(first["published_ids"] == expected, "native string sort preserves reference order including Unicode")
first["published_ids"].clear()
_expect(stream.create_snapshot()["published_ids"] == expected, "returned ID array cannot corrupt cached history")
var event := SimulationDomainEvent.new(&"new_id", &"TEST", 360, &"test", [], {}, 1)
_expect(stream.publish(event), "publish invalidates cached IDs")
expected.append("new_id");expected.sort()
_expect(stream.create_snapshot()["published_ids"] == expected, "new ID appears once after cached snapshot")
_expect(not stream.publish(event) and stream.create_snapshot()["published_ids"] == expected, "rejected duplicate preserves cached state")
_expect(stream.restore_snapshot({"version":1,"published_ids":["replacement"],"recent_events":[]}), "replace history through restore")
_expect(stream.create_snapshot()["published_ids"] == ["replacement"], "restore invalidates old sorted cache")
var valid := stream.create_snapshot()
_expect(not stream.restore_snapshot({"version":1,"published_ids":["duplicate","duplicate"],"recent_events":[]}), "invalid restore rejected")
_expect(stream.create_snapshot() == valid, "failed restore preserves canonical snapshot")
+2 -1
View File
@@ -66,7 +66,8 @@ func _test_tick_command_execution() -> void:
func _test_starter_world_layers() -> void:
_reset_runtime()
_expect(_runtime.definition_registry.facility_count() == 18, "Runtime loads baseline sixteen plus two required stair definitions")
_expect(_runtime.definition_registry.facility_count() == 23, "Runtime loads baseline sixteen plus stairs, the infirmary and premium room/dormitory and chapel")
_expect(_runtime.definition_registry.has_facility(&"chapel"), "Runtime exposes the original chapel definition")
var front_start := Vector3i(-2, 0, WorldService.LAYER_FRONT)
var back_goal := Vector3i(2, 0, WorldService.LAYER_BACK)
var guest_path: Array[Vector3i] = _runtime.path_service.find_path(
+56
View File
@@ -0,0 +1,56 @@
extends SceneTree
var checks:=0
var failures:=0
var runtime: Variant
var clock: Variant
var state: Variant
func expect(ok: bool, message: String) -> void:
checks+=1
if not ok: failures+=1;push_error(message)
func _initialize() -> void: call_deferred("run")
func build(id: StringName, origin: Vector3i) -> bool:
var result: CommandResult=runtime.submit_command(&"build_facility",{&"definition_id":id,&"origin":origin})
expect(result.is_success(),"legacy batch build %s" % id)
if not result.is_success(): return false
clock.advance_minutes(runtime.definition_registry.get_facility(id).build_minutes)
return true
func run() -> void:
runtime=load("res://autoload/simulation_runtime.gd").new();clock=load("res://autoload/game_clock.gd").new();state=load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,42506);clock.set_paused(true)
var paths: Array[String]=FacilityCatalog.FACILITY_PATHS+FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS+FacilityCatalog.RESCUE_FACILITY_PATHS
paths.append_array(["res://tests/fixtures/legacy_premium_room.tres","res://tests/fixtures/legacy_staff_dormitory.tres"])
expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"],"load both legacy definitions")
if not build(&"reception",Vector3i(-6,0,0)) or not build(&"premium_room",Vector3i(-6,2,0)) or not build(&"warehouse",Vector3i(-6,0,1)) or not build(&"staff_dormitory",Vector3i(-6,2,1)): finish();return
if not build(&"service_stairs",Vector3i(-7,1,1)) or not build(&"service_stairs",Vector3i(-7,2,1)) or not build(&"service_stairs",Vector3i(-7,3,1)): finish();return
paths.erase("res://tests/fixtures/legacy_premium_room.tres");paths.erase("res://tests/fixtures/legacy_staff_dormitory.tres");paths.append_array(FacilityCatalog.ORIGINAL_FACILITY_PATHS)
expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"],"switch batch to current definitions")
var before: Dictionary=runtime.create_snapshot()
var path: String="user://saves/dimension_batch_%d" % Time.get_ticks_usec()
expect(SaveService.save_snapshot(path,before)[&"ok"],"persist original two-facility source")
var migrated: Dictionary=runtime.migrate_facility_dimensions_at_path(path)
expect(migrated[&"ok"],"batch disk migration accepts both facilities")
var backup:=SaveService.load_snapshot(path+SaveService.MIGRATION_BACKUP_SUFFIX)
expect(backup[&"ok"] and backup[&"snapshot"]==before,"one backup contains both original footprints")
expect(runtime.create_snapshot()==before,"batch disk migration preserves live world")
var loaded:=SaveService.load_snapshot(path)
expect(loaded[&"ok"] and runtime.restore_snapshot(loaded[&"snapshot"]),"batch result restores all crosslinks")
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id==&"premium_room": expect(room.occupied_cells.size()==4,"batch premium footprint is four cells")
if room.definition_id==&"staff_dormitory": expect(room.occupied_cells.size()==2,"batch dormitory footprint is two cells")
var repeated: Dictionary=runtime.migrate_facility_dimensions_at_path(path)
expect(repeated[&"ok"] and not repeated.get(&"backup_created",true) and SaveService.load_snapshot(path+SaveService.MIGRATION_BACKUP_SUFFIX)[&"snapshot"]==before,"repeat batch preserves first backup")
expect(runtime.restore_snapshot(before),"restore source to test late batch failure")
var dorm_id: StringName=&""
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id==&"staff_dormitory": dorm_id=room.instance_id
expect(runtime.staff_service.create_task(&"maintenance",10,clock.absolute_minute,{&"target_entity_id":dorm_id,&"target_cell":Vector3i(-7,3,1)},&"batch_blocker").is_success(),"create valid task at disappearing second-facility entrance")
var rejected_source: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(rejected_source) and SaveService.save_snapshot(path,rejected_source)[&"ok"],"late-failure source is valid and saved")
var rejected: Dictionary=runtime.migrate_facility_dimensions_at_path(path)
expect(not rejected[&"ok"] and rejected.get(&"details",{}).get(&"facility_id",&"")==dorm_id and rejected[&"details"].get(&"rolled_back",false),"second facility failure rolls back isolated first-facility conversion")
expect(SaveService.load_snapshot(path)[&"snapshot"]==rejected_source and runtime.create_snapshot()==rejected_source,"late rejection preserves entire disk source and live state")
expect(SaveService.load_snapshot(path+SaveService.MIGRATION_BACKUP_SUFFIX)[&"snapshot"]==before,"late rejected batch leaves prior backup intact")
SaveService.delete_save(path);SaveService.delete_save(path+SaveService.MIGRATION_BACKUP_SUFFIX)
finish()
func finish() -> void:
runtime.free();clock.free();state.free();print("Dimension batch save: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://cr2qkdh3c3d2s
@@ -99,6 +99,7 @@ func _run() -> void:
_test_callback_reentry_tamper_and_quarantine_recovery()
_test_strict_snapshot_round_trip_and_atomic_rejection()
_test_configuration_and_stale_dependency_boundaries()
_test_validation_cache_detects_changes()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
@@ -459,6 +460,39 @@ func _test_configuration_and_stale_dependency_boundaries() -> void:
_expect(not collision_projector.configure(collision_adapter, collision_source, collision_path), "configuration rejects an untracked managed-ID namespace collision")
func _test_validation_cache_detects_changes() -> void:
var start := Vector3i(0, 0, PathType.FRONT)
var middle := Vector3i(1, 0, PathType.FRONT)
var goal := Vector3i(2, 0, PathType.FRONT)
var context := _context([start, goal])
var instance := _instance(GUEST_CORRIDOR, &"facility_cache", middle, 10, &"construction_cache")
_expect(context.projector.project_facility(&"project_cache", GUEST_CORRIDOR, instance).is_success(), "cache fixture projects corridor")
_expect(context.projector.validate_invariants()[&"ok"], "initial complete validation succeeds")
_expect(context.projector.validate_invariants()[&"ok"], "unchanged validation succeeds")
var before: Dictionary = context.projector.create_snapshot()
var exported: Dictionary = context.projector.create_snapshot()
exported[&"projections"][0][&"cells"].clear()
_expect(context.projector.validate_invariants()[&"ok"] and context.projector.create_snapshot() == before, "exported containers cannot alter validation cache or live state")
var records: Dictionary = context.projector.get("_projections")
var fingerprint: String = records[&"facility_cache"][&"record_fingerprint"]
records[&"facility_cache"][&"record_fingerprint"] = "invalid"
_expect(not context.projector.validate_invariants()[&"ok"], "warm cache detects changed projection content at unchanged revision")
records[&"facility_cache"][&"record_fingerprint"] = fingerprint
_expect(context.projector.validate_invariants()[&"ok"], "repaired owned state validates")
var requests: Dictionary = context.projector.get("_processed_requests")
var request_fingerprint: String = requests[&"project_cache"][&"fingerprint"]
requests[&"project_cache"][&"fingerprint"] = "invalid"
_expect(not context.projector.validate_invariants()[&"ok"], "warm cache detects changed request history")
requests[&"project_cache"][&"fingerprint"] = request_fingerprint
var path_snapshot: Dictionary = context.path.create_snapshot()
_expect(context.path.rebuild_graph([start, goal]), "inject live path change without projector mutation")
_expect(not context.projector.validate_invariants()[&"ok"], "unchanged owned snapshot still validates live dependencies")
_expect(context.path.rebuild_graph([start, middle, goal]) and context.path.restore_snapshot(path_snapshot), "restore valid live graph and traffic snapshot")
_expect(context.projector.validate_invariants()[&"ok"], "repaired dependencies validate against warm cache")
_expect(context.projector.unproject_facility(&"unproject_cache", &"facility_cache").is_success(), "new history after cache warm can unproject")
_expect(context.projector.validate_invariants()[&"ok"], "new history receives full validation")
func _context(base_cells: Array, custom_path: Variant = null) -> Dictionary:
var source := SpatialType.new()
var path: PathService = custom_path if custom_path != null else PathType.new()
+58
View File
@@ -0,0 +1,58 @@
extends SceneTree
const Preview = preload("res://scripts/construction/facility_resize_preview.gd")
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void:
var world := WorldService.new()
world.reset({&"min_x":-10,&"max_x":10,&"min_y":-5,&"max_y":10})
var origin := Vector3i(0,0,0)
var cells := world.cells_for_footprint(origin,Vector2i(3,2))
world.occupy(&"room",cells)
world.set_support(origin,0,true)
var item := {&"instance_id":&"room",&"definition_id":&"premium_room",&"origin":origin,&"rotation":0,&"occupied_cells":cells}
var before := world.create_snapshot()
var report := Preview.inspect(world,item,2)
expect(report[&"geometry_ok"] and report[&"target_cells"].size()==4 and report[&"added"].size()==1 and report[&"removed"].size()==3, "premium room preview changes 3x2 into 4x1")
expect(not report[&"ready_to_apply"] and not report[&"remaining_gates"].is_empty(), "geometry success never bypasses route save or job migration")
expect(world.create_snapshot()==before, "successful preview does not mutate live world")
world.occupy(&"neighbor",[Vector3i(3,0,0)])
before=world.create_snapshot();report=Preview.inspect(world,item,2)
expect(not report[&"geometry_ok"] and report[&"errors"][0][&"code"]==&"OVERLAP", "expansion detects an occupied new cell")
expect(world.create_snapshot()==before, "rejected preview preserves ownership and revisions")
world.release(&"neighbor")
world.occupy(&"upper",[Vector3i(2,2,0)])
report=Preview.inspect(world,item,2)
expect(not report[&"geometry_ok"] and &"upper" in report[&"affected_entities"], "shrink detects loss of support for upper facility")
var bad:=item.duplicate(true);bad[&"occupied_cells"][0]=Vector3i(9,9,0)
expect(not Preview.inspect(world,bad,2)[&"geometry_ok"], "source ownership cannot be guessed from current definition")
bad=item.duplicate(true);bad[&"occupied_cells"][1]=bad[&"occupied_cells"][0]
expect(not Preview.inspect(world,bad,2)[&"geometry_ok"], "duplicate saved cells reject")
bad=item.duplicate(true);bad[&"instance_id"]=42
expect(not Preview.inspect(world,bad,2)[&"geometry_ok"], "malformed saved identifiers reject without conversion errors")
bad=item.duplicate(true);bad[&"rotation"]=45
expect(not Preview.inspect(world,bad,2)[&"geometry_ok"], "invalid rotation rejects")
world.release(&"upper");world.release(&"room")
origin=Vector3i(0,0,1);cells=world.cells_for_footprint(origin,Vector2i(2,4))
world.occupy(&"dorm",cells);world.set_support(origin,0,true)
item={&"instance_id":&"dorm",&"definition_id":&"staff_dormitory",&"origin":origin,&"rotation":90,&"occupied_cells":cells}
report=Preview.inspect(world,item,1)
expect(report[&"geometry_ok"] and report[&"target_cells"]==[Vector3i(0,0,1),Vector3i(0,1,1)], "rotated dorm shrinks on correct layer and axis")
world.release(&"dorm")
origin=Vector3i(7,-3,0);cells=world.cells_for_footprint(origin,Vector2i(3,2))
world.occupy(&"market",cells);world.set_support(origin,0,true)
for cell: Vector3i in cells: world.set_excavated(cell,true)
item={&"instance_id":&"market",&"definition_id":&"black_market",&"origin":origin,&"rotation":0,&"occupied_cells":cells}
before=world.create_snapshot();report=Preview.inspect(world,item,2)
expect(not report[&"geometry_ok"] and world.create_snapshot()==before, "market cannot expand into unexcavated underground space")
world.set_excavated(Vector3i(10,-3,0),true);world.set_excavated(Vector3i(10,-2,0),true)
report=Preview.inspect(world,item,2)
expect(report[&"geometry_ok"] and report[&"target_cells"].size()==8 and report[&"added"].size()==2, "excavated market expands to original four-by-two footprint")
world.release(&"market")
origin=Vector3i(8,-3,0);cells=world.cells_for_footprint(origin,Vector2i(3,2))
world.occupy(&"edge",cells);world.set_support(origin,0,true)
item={&"instance_id":&"edge",&"definition_id":&"black_market",&"origin":origin,&"rotation":0,&"occupied_cells":cells}
expect(not Preview.inspect(world,item,2)[&"geometry_ok"], "market expansion cannot leave map bounds")
print("Facility resize preview: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://cja1om6ylyvfa
@@ -0,0 +1,46 @@
extends SceneTree
var checks := 0
var failures := 0
class Budget:
extends RefCounted
var resources := {&"gold":100000, &"materials":100000}
func get_resource(id: StringName) -> int: return resources.get(id, 0)
func can_afford(cost: Dictionary) -> bool:
for id: Variant in cost:
if resources.get(id, 0) < cost[id]: return false
return true
func spend(cost: Dictionary) -> bool:
if not can_afford(cost): return false
for id: Variant in cost: resources[id] -= cost[id]
return true
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void:
for entry: Array in [["premium_room",Vector2i(4,1),0,2],["staff_dormitory",Vector2i(2,1),1,2],["black_market",Vector2i(4,2),0,-5]]:
var definition: FacilityDefinition = load("res://data/facilities/expansion/%s.tres" % entry[0])
expect(definition.footprint == entry[1] and definition.layer == entry[2], "canonical dimensions and layer: " + entry[0])
for rotation: int in [0,90,180,270]:
var world := WorldService.new()
world.reset({&"min_x":-10,&"max_x":10,&"min_y":-10,&"max_y":10})
var origin := Vector3i(0,entry[3],entry[2])
var size: Vector2i = entry[1]
if rotation in [90,270]: size = Vector2i(size.y,size.x)
var expected := world.cells_for_footprint(origin,size)
world.set_support(origin,0,true)
for cell: Vector3i in expected:
if cell.y < 0: world.set_excavated(cell,true)
var budget := Budget.new()
var construction := ConstructionService.new(world,budget)
var started := construction.start_construction(definition,origin,rotation)
expect(started.is_success(), "canonical construction accepted: %s rotation %d" % [entry[0],rotation])
if not started.is_success(): continue
var job := construction.get_jobs()[0]
expect(job.reserved_cells == expected, "reservation has exact rotated original footprint")
for minute in range(1,definition.build_minutes+1): construction.advance_one_minute(minute)
var completed := construction.get_facility(job.facility_instance_id)
expect(job.state == ConstructionJob.State.COMPLETED and completed.get(&"occupied_cells",[]) == expected and world.get_entity_cells(job.facility_instance_id).size() == expected.size(), "completion keeps original footprint")
expect(budget.get_resource(&"gold") == 100000-int(definition.construction_cost[&"gold"]), "dimension correction does not multiply construction payment")
var restored := ConstructionService.new(world,budget)
expect(restored.restore_snapshot(construction.create_snapshot()), "completed original-size construction restores")
print("Original facility dimensions: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://crwpol62ko88p
+2 -2
View File
@@ -59,7 +59,7 @@ func _test_cell_traffic_and_dynamic_route() -> void:
_expect(int(traffic[&"current_density"]) == 4, "cell reports projected current density")
_expect(int(traffic[&"dynamic_cost"]) == 25, "cell reports configured dynamic cost")
_expect(service.get_congestion_cost(top_middle) == 225, "density excess costs 75 units per actor")
_expect(is_equal_approx(service.get_speed_multiplier(top_middle), 0.25), "soft collision speed clamps at 0.25")
_expect(is_equal_approx(service.get_speed_multiplier(top_middle), 0.5), "overlapping units move at half speed")
var diverted: Array[Vector3i] = service.find_path(start, goal, service.FRONT)
_expect(diverted.has(bottom_middle), "A* avoids the dynamically congested route")
_expect(not diverted.has(top_middle), "locally invalidated cache never returns the stale congested route")
@@ -118,7 +118,7 @@ func _test_two_hundred_actor_chokepoint() -> void:
_expect(all_entered, "all 200 actors can share the chokepoint without a hard-cap deadlock")
_expect(service.get_current_density(choke) == 200, "chokepoint density reaches 200 exactly")
_expect(service.get_congestion_cost(choke) == 14_700, "200-actor chokepoint congestion cost is exact")
_expect(is_equal_approx(service.get_speed_multiplier(choke), 0.25), "extreme density remains bounded by minimum speed")
_expect(is_equal_approx(service.get_speed_multiplier(choke), 0.5), "extreme density preserves the specified half speed")
var all_exited := true
for index: int in range(200):
var actor_id := StringName("guest_%03d" % index)
@@ -63,6 +63,7 @@ func _run() -> void:
_test_uncertain_rollback_quarantines_and_recovers()
_test_strict_snapshot_round_trip_and_atomic_rejection()
_test_strict_input_and_route_only_boundary()
_test_validation_cache_checks_history_and_live_graph()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
@@ -450,6 +451,35 @@ func _test_strict_input_and_route_only_boundary() -> void:
_expect(not adapter.configure(source, path, [Vector3i.ZERO]), "configured adapter rejects reconfiguration")
func _test_validation_cache_checks_history_and_live_graph() -> void:
var source := SpatialType.new()
var path := PathType.new()
var adapter := AdapterType.new()
var start := Vector3i(0, 0, 0)
var middle := Vector3i(1, 0, 0)
var goal := Vector3i(2, 0, 0)
_expect(adapter.configure(source, path, [start, goal]), "cache fixture configures")
_expect(adapter.place_route(&"cache_place", &"cache_route", [middle]).is_success(), "cache fixture places route")
_expect(adapter.validate_invariants()[&"ok"] and adapter.validate_invariants()[&"ok"], "fresh and unchanged validations succeed")
var exported := adapter.create_snapshot()
exported[&"projected_walkable_cells"].clear()
exported[&"processed_requests"][0][&"input_cells"].clear()
_expect(adapter.validate_invariants()[&"ok"], "snapshot mutation cannot change live state or cached input")
var requests: Dictionary = adapter.get("_processed_requests")
var fingerprint: String = requests[&"cache_place"][&"fingerprint"]
requests[&"cache_place"][&"fingerprint"] = "invalid"
_expect(not adapter.validate_invariants()[&"ok"], "warm cache detects history corruption without revision change")
requests[&"cache_place"][&"fingerprint"] = fingerprint
_expect(adapter.validate_invariants()[&"ok"], "restored history validates")
var before := path.create_snapshot()
_expect(path.rebuild_graph([start, goal]), "remove live bridge independently")
_expect(not adapter.validate_invariants()[&"ok"], "warm cache still detects live graph divergence")
_expect(path.rebuild_graph([start, middle, goal]) and path.restore_snapshot(before), "restore live path")
_expect(adapter.validate_invariants()[&"ok"], "repaired graph validates")
_expect(adapter.remove_route(&"cache_remove", &"cache_route").is_success(), "route removal updates cached history input")
_expect(adapter.validate_invariants()[&"ok"], "updated history validates fully")
func _assert_exact_rollback(
adapter: RouteSubslotPathAdapter,
source: SpatialSubslotService,
@@ -1,6 +1,6 @@
extends SceneTree
const RUNTIME_SNAPSHOT_VERSION := 9
const RUNTIME_SNAPSHOT_VERSION := 13
const INFRASTRUCTURE_SNAPSHOT_KEYS: Array[StringName] = [
&"excavation",
&"spatial_subslots",
@@ -39,6 +39,22 @@ class SnapshotCountingDomainEventStream:
return super.create_snapshot()
class CountingSpatialValidation:
extends SpatialSubslotService
var validation_calls := 0
func validate_invariants() -> Dictionary:
validation_calls += 1
return super.validate_invariants()
class CountingAdapterValidation:
extends RouteSubslotPathAdapter
var validation_calls := 0
func validate_invariants() -> Dictionary:
validation_calls += 1
return super.validate_invariants()
var _checks := 0
var _failures := 0
@@ -51,8 +67,10 @@ func _run() -> void:
print("S1: Runtime infrastructure integration")
var probe := _new_fixture(81_001)
var surface_ready := _test_runtime_surface(probe)
_test_exact_cell_sets(probe[&"runtime"])
_free_fixture(probe)
if surface_ready:
_test_composed_infrastructure_validation()
_test_completion_capture_fast_path()
_test_rank_and_operations_gates()
_test_excavation_tick_pause_resume_and_completion()
@@ -75,6 +93,61 @@ func _finish() -> void:
quit(_failures)
func _test_composed_infrastructure_validation() -> void:
var fixture := _new_fixture(81_099)
var runtime: Variant = fixture[&"runtime"]
var spatial := CountingSpatialValidation.new()
var adapter := CountingAdapterValidation.new()
var path := PathService.new()
var projector := FacilityInfrastructureProjector.new()
_expect(adapter.configure(spatial, path, runtime.world_service.get_walkable_cells()), "counted route dependencies configure")
_expect(projector.configure(adapter, spatial, path), "counted projector binds authoritative dependencies")
var validate := func() -> bool:
return runtime.call("_validate_infrastructure_crosslinks", runtime.world_service, path,
runtime.construction_service, runtime.construction_resource_state, runtime.excavation_service,
spatial, adapter, projector)
spatial.validation_calls = 0
adapter.validation_calls = 0
_expect(validate.call(), "composed infrastructure validation accepts healthy graph")
_expect(spatial.validation_calls == 1 and adapter.validation_calls == 1, "one graph audit validates each dependency exactly once")
_expect(projector.is_bound_to(adapter, spatial, path) and not projector.is_bound_to(adapter, SpatialSubslotService.new(), path), "binding test requires identical dependency objects")
spatial.revision = -1
_expect(not validate.call(), "active composed check still catches raw source corruption")
spatial.revision = 0
var prior_revision := adapter.projection_revision
adapter.projection_revision = -1
_expect(not validate.call(), "active composed check still catches raw adapter corruption")
adapter.projection_revision = prior_revision
projector.set("_quarantined", true)
projector.set("_quarantine_reason", FacilityInfrastructureProjector.QUARANTINE_REASONS[0])
spatial.validation_calls = 0
adapter.validation_calls = 0
_expect(validate.call(), "canonical quarantined projector retains previous graph-validation semantics")
_expect(spatial.validation_calls == 1 and adapter.validation_calls == 1, "quarantine retains independent dependency checks")
spatial.revision = -1
_expect(not validate.call(), "quarantine never conceals malformed supplied source")
spatial.revision = 0
projector.set("_quarantined", false)
projector.set("_quarantine_reason", &"")
var original_projector: FacilityInfrastructureProjector = runtime.facility_infrastructure_projector
spatial.revision = -1
_expect(not runtime.call("_validate_infrastructure_crosslinks", runtime.world_service, path,
runtime.construction_service, runtime.construction_resource_state, runtime.excavation_service,
spatial, adapter, original_projector), "different projector binding does not bypass supplied-source validation")
_free_fixture(fixture)
func _test_exact_cell_sets(runtime: Variant) -> void:
var first := Vector3i(0, 0, 0)
var second := Vector3i(1, 0, 0)
_expect(runtime.call("_same_vector3i_set", [first, second], [second, first]), "cell equality ignores order")
_expect(not runtime.call("_same_vector3i_set", [first, second], [first, first]), "duplicate actual cell cannot conceal a missing cell")
_expect(not runtime.call("_same_vector3i_set", [first, first], [first, second]), "duplicate expected cell is rejected")
_expect(runtime.call("_same_vector3i_set", [], []), "empty cell sets match")
_expect(not runtime.call("_same_vector3i_set", [first], [Vector3i(0, 0, 1)]), "same coordinate in another layer does not match")
_expect(not runtime.call("_same_vector3i_set", [first], [Vector2i.ZERO]), "wrong cell type is rejected")
func _new_fixture(seed: int) -> Dictionary:
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
@@ -113,7 +186,7 @@ func _test_runtime_surface(fixture: Dictionary) -> bool:
var snapshot: Dictionary = runtime.create_snapshot()
var runtime_data: Dictionary = snapshot[&"systems"][&"runtime"]
var current_version := int(runtime_data.get(&"runtime_schema_version", -1)) == RUNTIME_SNAPSHOT_VERSION
_expect(current_version, "Runtime emits schema v9 with infrastructure state")
_expect(current_version, "Runtime emits schema v13 with infrastructure and weather state")
ready = ready and current_version
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
var valid_dictionary := runtime_data.get(key, null) is Dictionary
@@ -827,6 +900,7 @@ func _test_v5_proxy_migration_determinism() -> void:
legacy_runtime.erase(&"kitchen_production")
legacy_runtime.erase(&"elevators")
legacy_runtime.erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_runtime)
legacy_runtime.erase(&"staff_commute")
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
legacy_runtime.erase(key)
@@ -849,7 +923,7 @@ func _test_v5_proxy_migration_determinism() -> void:
_expect(first_runtime.restore_snapshot(legacy), "v5 proxy snapshot migrates through Runtime restore")
var first_migrated: Dictionary = first_runtime.create_snapshot()
_expect(
int(first_migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 9,
int(first_migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 13,
"v5 migration is immediately re-emitted as v9"
)
_expect(
+188
View File
@@ -0,0 +1,188 @@
extends SceneTree
const Preview = preload("res://scripts/construction/facility_resize_preview.gd")
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok: failures += 1; push_error(message)
func _initialize() -> void: call_deferred("run")
func run() -> void:
var world := WorldService.new()
world.reset({&"min_x":-10,&"max_x":10,&"min_y":-5,&"max_y":10})
var origin := Vector3i(0,-3,0)
var cells := world.cells_for_footprint(origin,Vector2i(3,2))
world.occupy(&"market",cells);world.occupy(&"heavy",[Vector3i(0,-4,0)])
world.set_support(origin,10)
for cell: Vector3i in world.cells_for_footprint(origin,Vector2i(4,2)): world.set_excavated(cell,true)
var item := {&"instance_id":&"market",&"definition_id":&"black_market",&"origin":origin,&"rotation":0,&"occupied_cells":cells}
var weights := {&"market":1,&"heavy":3}
expect(world.validate_support(cells,1,weights)[&"ok"], "original occupied structure fits exact capacity")
expect(Preview.inspect(world,item,1)[&"geometry_ok"], "generic occupant weights alone miss the overload")
var before := world.create_snapshot()
var preview := Preview.inspect(world,item,1,weights)
expect(not preview[&"geometry_ok"] and &"TARGET_UNSUPPORTED" in preview[&"errors"], "exact neighboring definition weight rejects overloaded expansion")
expect(world.create_snapshot()==before, "weighted validation does not mutate world")
expect(not world.validate_support(cells,1,{&"missing":2})[&"ok"] and not world.validate_support(cells,1,{&"heavy":-1})[&"ok"], "unknown and negative weight overrides reject")
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();runtime.initialize(clock,state,424242);clock.set_paused(true)
var paths: Array[String] = FacilityCatalog.FACILITY_PATHS + FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS + FacilityCatalog.RESCUE_FACILITY_PATHS
paths.append("res://tests/fixtures/legacy_premium_room.tres")
expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"], "fixture explicitly registers archived legacy-size definition")
expect(runtime.submit_command(&"build_facility",{&"definition_id":&"reception",&"origin":Vector3i(-6,0,0)}).is_success(), "build supporting reception at ground level")
clock.advance_minutes(120)
var built: CommandResult = runtime.submit_command(&"build_facility",{&"definition_id":&"premium_room",&"origin":Vector3i(-6,2,0)})
expect(built.is_success(), "build above-ground old-size room: " + built.message)
if not built.is_success():
runtime.free();clock.free();state.free();quit(failures);return
var active_id: StringName = &""
for job: ConstructionJob in runtime.construction_service.get_jobs():
if job.definition_id == &"premium_room" and not job.is_terminal(): active_id = job.job_id
paths.erase("res://tests/fixtures/legacy_premium_room.tres")
paths.append("res://data/facilities/expansion/premium_room.tres")
expect(runtime.definition_registry.rebuild_from_paths(paths)[&"valid"], "current original-size registry replaces legacy fixture after reservation")
clock.advance_minutes(7)
var active_before: Dictionary = runtime.create_snapshot()
var original_job: Dictionary = runtime.construction_service.get_job(active_id).create_snapshot()
var active_path := "user://saves/active_resize_%d" % Time.get_ticks_usec()
expect(SaveService.save_snapshot(active_path,active_before)[&"ok"], "save partially built legacy job")
var active_migration: Dictionary=runtime.migrate_facility_dimensions_at_path(active_path)
expect(active_migration[&"ok"] and active_migration.get(&"backup_created",false), "batch migrates partial job with verified backup")
var migrated_job_save := SaveService.load_snapshot(active_path)
expect(migrated_job_save[&"ok"] and runtime.restore_snapshot(migrated_job_save[&"snapshot"]), "full Runtime restores migrated construction reservation")
var current_job: ConstructionJob=runtime.construction_service.get_job(active_id)
expect(current_job.reserved_cells.size()==4 and current_job.progress==original_job[&"progress"] and current_job.construction_cost==original_job[&"construction_cost"], "full Runtime migration preserves job progress and paid cost")
clock.advance_minutes(150)
var finished_job: ConstructionJob=runtime.construction_service.get_job(active_id)
expect(finished_job.state==ConstructionJob.State.COMPLETED and runtime.facility_service.get_instance(finished_job.facility_instance_id).occupied_cells.size()==4, "migrated partial job completes as original-size operational room")
expect(runtime.restore_snapshot(runtime.create_snapshot()), "completion after full job migration remains saveable")
expect(SaveService.load_snapshot(active_path+SaveService.MIGRATION_BACKUP_SUFFIX)[&"snapshot"]==active_before and runtime.restore_snapshot(active_before), "original job backup remains intact and legacy fixture can resume")
SaveService.delete_save(active_path);SaveService.delete_save(active_path+SaveService.MIGRATION_BACKUP_SUFFIX)
var candidate := Preview.prepare_construction_candidate(runtime.world_service, runtime.construction_service, active_id, 2)
expect(candidate[&"ok"] and not candidate[&"ready_to_apply"], "active construction has a validated candidate, not a Runtime commit")
if candidate[&"ok"]:
var active_row: Dictionary = {}
for row: Dictionary in candidate[&"construction"][&"jobs"]:
if StringName(row[&"job_id"]) == active_id: active_row = row
expect(active_row[&"reserved_cells"].size() == 4 and active_row[&"progress"] == original_job[&"progress"] and active_row[&"progress"] > 0, "partially built job converts without advancing paid work")
var resumed_world := WorldService.new()
expect(resumed_world.restore_snapshot(candidate[&"world"]), "candidate world restores for partial-job continuation")
var resumed := ConstructionService.new(resumed_world)
expect(resumed.restore_snapshot(candidate[&"construction"]), "partial job restores through production validator")
var job_before := resumed.get_job(active_id).create_snapshot()
var replay := resumed.progress_job(active_id, ConstructionJob.NORMAL_WORK_RATE, resumed.current_minute)
expect(replay.is_success() and replay.is_replay and resumed.get_job(active_id).create_snapshot() == job_before, "last saved construction minute is not charged as work twice")
var completion_count := [0]
resumed.facility_completed.connect(func(_facility: Dictionary): completion_count[0] += 1)
for minute in range(resumed.current_minute + 1, resumed.current_minute + 201): resumed.advance_one_minute(minute)
var finished := resumed.get_job(active_id)
expect(finished.state == ConstructionJob.State.COMPLETED and completion_count[0] == 1, "resized partial job completes exactly once")
var finished_room := resumed.get_facility(finished.facility_instance_id)
expect(finished_room[&"occupied_cells"].size() == 4 and resumed_world.get_entity_cells(finished.facility_instance_id) == finished_room[&"occupied_cells"], "completion occupies original-spec footprint with new facility ownership")
expect(finished.construction_cost == original_job[&"construction_cost"] and finished.build_minutes == original_job[&"build_minutes"] and finished.started_at == original_job[&"started_at"], "migration and completion retain paid cost, duration and start time")
var completed_restore := ConstructionService.new(resumed_world)
expect(completed_restore.restore_snapshot(resumed.create_snapshot()), "completed migrated source job and facility remain save-compatible")
expect(runtime.create_snapshot() == active_before, "active candidate leaves source world, jobs and money unchanged")
clock.advance_minutes(150)
var id: StringName = &""
for facility: Dictionary in runtime.construction_service.get_facilities():
if facility[&"definition_id"] == &"premium_room": id = facility[&"instance_id"]
expect(not id.is_empty(), "above-ground expansion completes")
if id.is_empty():
runtime.free();clock.free();state.free();quit(failures);return
var full_before: Dictionary = runtime.create_snapshot()
var disk_path := "user://saves/resize_disk_%d" % Time.get_ticks_usec()
expect(SaveService.save_snapshot(disk_path, full_before)[&"ok"], "write isolated legacy-size disk fixture")
var disk_migration: Dictionary = runtime.migrate_facility_dimensions_at_path(disk_path, id)
expect(disk_migration[&"ok"] and disk_migration.get(&"backup_created",false), "disk migration verifies candidate and creates backup before promotion")
var disk_loaded := SaveService.load_snapshot(disk_path)
var disk_backup := SaveService.load_snapshot(disk_path + SaveService.MIGRATION_BACKUP_SUFFIX)
expect(disk_backup[&"ok"] and disk_backup[&"snapshot"] == full_before, "pre-migration disk backup retains original full snapshot")
var backup_bytes := FileAccess.get_file_as_bytes(ProjectSettings.globalize_path(disk_path + SaveService.MIGRATION_BACKUP_SUFFIX + "/world.bin"))
expect(runtime.migrate_facility_dimensions_at_path(disk_path,id)[&"ok"] and FileAccess.get_file_as_bytes(ProjectSettings.globalize_path(disk_path + SaveService.MIGRATION_BACKUP_SUFFIX + "/world.bin")) == backup_bytes, "repeated migration preserves original backup")
var alternate: Dictionary = full_before.duplicate(true)
alternate[&"clock"][&"absolute_minute"] += 1
var stale := SaveService.replace_snapshot_with_backup(disk_path, full_before, alternate)
expect(not stale[&"ok"] and stale[&"code"] == &"MIGRATION_SOURCE_CHANGED" and SaveService.load_snapshot(disk_path)[&"snapshot"] == disk_loaded[&"snapshot"], "stale source rejects before overwriting migrated save")
var rejected_invalid := SaveService.replace_snapshot_with_backup(disk_path, disk_loaded[&"snapshot"], {&"invalid":true})
expect(not rejected_invalid[&"ok"] and rejected_invalid[&"code"] == &"MIGRATION_INVALID_SNAPSHOT" and SaveService.load_snapshot(disk_path)[&"snapshot"] == disk_loaded[&"snapshot"], "invalid candidate preserves formal package")
expect(FileAccess.get_file_as_bytes(ProjectSettings.globalize_path(disk_path + SaveService.MIGRATION_BACKUP_SUFFIX + "/world.bin")) == backup_bytes, "failed migration preserves original backup bytes")
expect(runtime.create_snapshot() == full_before, "disk migration does not alter live Runtime")
var full_candidate: Dictionary = runtime.prepare_facility_dimension_snapshot(id)
expect(full_candidate[&"ok"] and runtime.create_snapshot() == full_before, "full snapshot candidate preserves live Runtime")
var isolated: Variant = load("res://autoload/simulation_runtime.gd").new()
var isolated_clock: Variant = load("res://autoload/game_clock.gd").new()
var isolated_state: Variant = load("res://autoload/game_state.gd").new()
isolated_clock.reset(1,6,0);isolated_state.reset();isolated.initialize(isolated_clock,isolated_state,424242)
isolated.definition_registry.rebuild_from_paths(paths)
expect(isolated.restore_snapshot(full_candidate[&"snapshot"]), "full migrated snapshot passes Runtime crosslinks in isolated instance")
expect(isolated.restore_snapshot(disk_loaded[&"snapshot"]) and isolated.facility_service.get_instance(id).occupied_cells.size() == 4, "migrated disk package restores original footprint in separate Runtime")
SaveService.delete_save(disk_path)
SaveService.delete_save(disk_path + SaveService.MIGRATION_BACKUP_SUFFIX)
expect(isolated.facility_service.get_instance(id).occupied_cells.size() == 4, "operating facility restores original-spec footprint")
var route_report := preload("res://scripts/construction/facility_resize_routes.gd").inspect(runtime, isolated, id)
expect(route_report[&"ok"] and route_report[&"checked_routes"] == 1, "isolated Runtime route comparison preserves existing entrance reachability")
# Controlled topology fixture for the route comparator, separate from the
# valid full-snapshot acceptance above. Upper-floor-only access is lost.
var old_graph: Array[Vector3i] = [Vector3i(-9,3,0), Vector3i(-8,3,0), Vector3i(-7,3,0)]
runtime.path_service.rebuild_graph(old_graph); isolated.path_service.rebuild_graph(old_graph)
var guest_type: StringName = runtime.get_mvp_guest_definition_ids()[0]
var observer := GuestRuntime.new().configure(&"route_observer", guest_type, {&"current_cell":old_graph[0], &"state":GuestRuntime.State.MOVING, &"target_facility_id":id, &"path_handle":{&"target_cell":old_graph[-1]}})
expect(runtime.guest_service.register_guest(observer, false) and isolated.guest_service.register_guest(GuestRuntime.from_snapshot(observer.create_snapshot()), false), "route fixture registers matching moving guests")
route_report = preload("res://scripts/construction/facility_resize_routes.gd").inspect(runtime, isolated, id)
expect(not route_report[&"ok"] and route_report[&"errors"].any(func(error: Variant): return error is Dictionary and error[&"code"] == &"GUEST_ACCESS_LOST"), "shrinking upper row detects loss of guest access even with unchanged path graph")
# A replacement lower access remains reachable, but saved target needs update.
old_graph.append_array([Vector3i(-9,2,0),Vector3i(-8,2,0),Vector3i(-7,2,0)])
runtime.path_service.rebuild_graph(old_graph);isolated.path_service.rebuild_graph(old_graph)
expect(runtime.path_service.add_connector(&"resize_stair",Vector3i(-9,3,0),Vector3i(-9,2,0)) and isolated.path_service.add_connector(&"resize_stair",Vector3i(-9,3,0),Vector3i(-9,2,0)), "fixture explicitly connects floors through a vertical connector")
route_report = preload("res://scripts/construction/facility_resize_routes.gd").inspect(runtime, isolated, id)
expect(route_report[&"errors"].is_empty() and route_report[&"guest_replans"].size() == 1 and not route_report[&"ok"], "reachable replacement still requires explicit moving-guest retargeting")
var source_guest: Dictionary = runtime.guest_service.get_guest(&"route_observer").create_snapshot()
var candidate_guest: Dictionary = isolated.guest_service.get_guest(&"route_observer").create_snapshot()
var invalid_batch: Dictionary = route_report.duplicate(true)
invalid_batch[&"guest_replans"].append({&"id":&"missing", &"old_target":Vector3i.ZERO, &"target":Vector3i.ZERO})
var rejected := preload("res://scripts/construction/facility_resize_routes.gd").retarget_guests(isolated, invalid_batch)
expect(not rejected[&"ok"] and isolated.guest_service.get_guest(&"route_observer").create_snapshot() == candidate_guest, "invalid later replan leaves earlier guest unchanged")
var applied := preload("res://scripts/construction/facility_resize_routes.gd").retarget_guests(isolated, route_report)
expect(applied[&"ok"] and applied[&"changed"] == [&"route_observer"], "retarget batch applies reachable entrance to isolated guest")
var after_guest: Dictionary = isolated.guest_service.get_guest(&"route_observer").create_snapshot()
after_guest[&"path_handle"] = candidate_guest[&"path_handle"]
expect(after_guest == candidate_guest and runtime.guest_service.get_guest(&"route_observer").create_snapshot() == source_guest, "retarget preserves position, wallet, service state and source guest")
expect(not preload("res://scripts/construction/facility_resize_routes.gd").retarget_guests(isolated, route_report)[&"ok"], "stale original destination cannot overwrite an already updated route")
route_report = preload("res://scripts/construction/facility_resize_routes.gd").inspect(runtime, isolated, id)
expect(route_report[&"ok"], "corrected moving target passes route comparison")
var moving: GuestRuntime = isolated.guest_service.get_guest(&"route_observer")
var actor_id: StringName = isolated._guest_path_actor_id(moving.guest_id)
expect(isolated.path_service.project_actor_position(actor_id, moving.current_cell, isolated_clock.absolute_minute), "project candidate guest for actual movement")
for offset in range(1, 21):
isolated._advance_guest_path_travel(moving, actor_id, isolated_clock.absolute_minute + offset)
if moving.current_cell.y == 2: break
expect(moving.current_cell == Vector3i(-9,2,0), "production movement follows retargeted lower entrance instead of old upper entrance")
expect(runtime.restore_snapshot(full_before), "restore valid source after controlled topology fixture")
isolated.free();isolated_clock.free();isolated_state.free()
expect(runtime.submit_command(&"register_staff",{&"staff_id":&"resize_observer",&"role_id":&"clerk",&"options":{&"current_cell":Vector3i.ZERO}}).is_success(), "register diagnostic worker on an existing corridor")
# Diagnostic legacy-position input only; no claim of normal navigation to an
# upper decorative cell. Preview must report it, never relocate it silently.
runtime.staff_service.get_staff(&"resize_observer").current_cell = Vector3i(-6,3,0)
expect(not runtime.prepare_facility_dimension_snapshot(id)[&"ok"], "removed-cell actor requires explicit relocation before preparing full snapshot")
var completed_before: Dictionary = runtime.create_snapshot()
candidate = Preview.prepare_construction_candidate(runtime.world_service, runtime.construction_service, id, 2)
expect(candidate[&"ok"], "completed facility and source job migrate together")
if candidate[&"ok"]:
var migrated_world := WorldService.new()
migrated_world.restore_snapshot(candidate[&"world"])
var migrated := ConstructionService.new(migrated_world)
expect(migrated.restore_snapshot(candidate[&"construction"]) and migrated.get_facility(id)[&"occupied_cells"].size() == 4, "candidate restores under production construction crosslink validation")
var retry := Preview.prepare_construction_candidate(migrated_world, migrated, id, 2)
expect(retry[&"ok"] and retry[&"construction"] == candidate[&"construction"] and retry[&"world"] == candidate[&"world"], "already migrated candidate is idempotent")
expect(runtime.create_snapshot() == completed_before, "completed candidate leaves source state untouched")
var snapshot: Dictionary = runtime.create_snapshot()
preview=runtime.preview_facility_dimension_migration(id)
expect(preview[&"construction_candidate"][&"ok"], "Runtime preview exposes validated construction candidate")
expect(preview[&"target_cells"].size()==4 and preview[&"definition_weights"][id]==2, "Runtime preview uses original dimension and live definition weight")
expect(preview[&"actors_on_removed_cells"].size()==1 and preview[&"actors_on_removed_cells"][0][&"id"]==&"resize_observer", "Runtime preview identifies actual worker requiring relocation")
expect(not preview[&"ready_to_apply"] and runtime.create_snapshot()==snapshot, "Runtime preview preserves all authoritative simulation state")
expect(not runtime.preview_facility_dimension_migration(&"missing")[&"ready_to_apply"], "unknown completed facility rejected")
runtime.free();clock.free();state.free()
print("Weighted resize Runtime: %d checks, %d failures" % [checks,failures]);quit(failures)
@@ -0,0 +1 @@
uid://buaihdar1qamn
+3 -2
View File
@@ -41,7 +41,8 @@ func _test_state_priority_and_opening_hours() -> void:
&"degraded_reasons": [&"DIRTY"],
}, 600)
_expect(facility.state == FacilityInstanceType.State.BROKEN, "BROKEN wins over every other state")
_expect(facility.state_reasons.size() == 6, "all simultaneous state reasons are retained")
_expect(facility.state_reasons.size() == 7, "all simultaneous state reasons are retained")
facility = _facility(&"undamaged_room", 1, 4, 1, 2)
facility.set_operational_conditions({
&"durability": 5_000,
&"access_available": false,
@@ -50,7 +51,7 @@ func _test_state_priority_and_opening_hours() -> void:
&"active_staff_ids": [],
&"degraded_reasons": [],
}, 600)
_expect(facility.state == FacilityInstanceType.State.NO_ACCESS, "NO_ACCESS wins after repair")
_expect(facility.state == FacilityInstanceType.State.NO_ACCESS, "NO_ACCESS wins in an undamaged facility")
facility.set_operational_conditions({&"access_available": true}, 600)
_expect(facility.state == FacilityInstanceType.State.NO_POWER, "NO_POWER wins over staffing and closure")
facility.set_operational_conditions({&"power_available": true}, 600)
+89
View File
@@ -0,0 +1,89 @@
extends SceneTree
const Accrual = preload("res://scripts/economy/facility_upkeep_accrual.gd")
var checks := 0
var failures := 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 service := Accrual.new()
expect(not service.advance_to(1, {}).ok, "an explicit starting minute is required; no retroactive fees")
expect(service.start_at(360).ok, "start at actual migration/start time")
expect(not service.start_at(0).ok, "starting twice cannot wipe accumulated charges")
service.advance_to(720, {&"room": 24, &"empty": 0})
expect(service.get_report().pending_facility_units == {"room": 8640}, "six hours at 24/day accrues exactly six gold")
service.advance_to(1080, {&"room": 24, &"new_room": 48})
var checkpoint := service.create_snapshot()
var restored := Accrual.new()
expect(restored.restore_snapshot(checkpoint), "partial day can be restored")
# The absent room has been demolished; its already accrued expense remains.
var close := service.advance_to(1440, {&"new_room": 48})
restored.advance_to(1440, {&"new_room": 48})
expect(close.closed_periods.size() == 1, "00:00 closes exactly one period")
var period: Dictionary = close.closed_periods[0]
expect(period.period_start == 360 and period.period_end == 1440, "first partial day never bills time before initialization")
expect(period.facility_units == {"new_room": 34560, "room": 17280}, "demolition stops future accrual but preserves incurred costs")
expect(service.create_snapshot() == restored.create_snapshot(), "save/load preserves exact fractional accumulation")
var before := service.create_snapshot()
expect(service.advance_to(1440, {&"new_room": 48}).ok and service.create_snapshot() == before, "same-minute replay cannot charge twice")
expect(not service.advance_to(1439, {}).ok and service.create_snapshot() == before, "rewind rejected without mutation")
close.closed_periods[0].facility_units.room = 999
expect(service.create_snapshot() == before, "returned period cannot mutate authoritative accrual")
var report := service.get_report()
report.closed_periods.clear()
expect(service.create_snapshot() == before, "report is a detached copy")
for rates: Dictionary in [{"bad": -1}, {"bad": 0.5}, {"bad": true}, {"": 1}, {"bad": Accrual.MAX_DAILY_RATE + 1}, {1: 5}]:
expect(not service.advance_to(1441, rates).ok and service.create_snapshot() == before, "invalid rates reject atomically: %s" % str(rates))
expect(not service.advance_to(2881, {}).ok and service.create_snapshot() == before, "unbounded batch rejected")
# Integer numerator is retained; neither per-minute nor per-day truncation.
var fractional := Accrual.new()
fractional.start_at(1439)
fractional.advance_to(1441, {"one_gold_per_day": 1})
var fraction_report := fractional.get_report()
expect(fraction_report.closed_periods[0].facility_units.one_gold_per_day == 1, "one minute charge is not rounded to zero")
expect(fraction_report.pending_facility_units.one_gold_per_day == 1, "midnight split conserves the second minute")
expect(fraction_report.gold_denominator == 1440, "settlement receives exact monetary unit denominator")
var split := Accrual.new()
var batched := Accrual.new()
split.start_at(0)
batched.start_at(0)
for minute in range(1, 1441):
split.advance_to(minute, {"b": 17, "a": 5})
batched.advance_to(1440, {"a": 5, "b": 17})
expect(split.create_snapshot() == batched.create_snapshot(), "input order and batching cannot change charges")
var json_copy: Dictionary = JSON.parse_string(JSON.stringify(split.create_snapshot()))
# JSON.parse_string produces floats; the project's snapshot codec handles
# integer preservation. Do not accept lossy/unvalidated raw JSON as a save.
expect(not restored.restore_snapshot(json_copy), "raw floating JSON fields are not silently accepted as integers")
var valid := split.create_snapshot()
var fresh := Accrual.new()
expect(fresh.restore_snapshot(valid), "closed periods restore")
for change: Dictionary in [
{"version": 2}, {"current_minute": 1439}, {"started_at": -2},
{"period_start": 1441}, {"extra": 1}, {"pending_units": {"room": 1}},
{"closed_periods": []}, {"current_minute": true},
]:
var invalid := valid.duplicate(true)
invalid.merge(change, true)
expect(not fresh.restore_snapshot(invalid) and fresh.create_snapshot() == valid, "invalid save rejected atomically: %s" % str(change))
var invalid := valid.duplicate(true)
invalid.closed_periods.append(invalid.closed_periods[0].duplicate(true))
expect(not fresh.restore_snapshot(invalid) and fresh.create_snapshot() == valid, "duplicated financial period rejected")
invalid = valid.duplicate(true)
invalid.closed_periods[0].facility_units.a = Accrual.MAX_DAILY_RATE * 1440 + 1
expect(not fresh.restore_snapshot(invalid), "impossible stored amount rejected")
invalid = valid.duplicate(true)
invalid.closed_periods[0].period_start = 1
expect(not fresh.restore_snapshot(invalid), "period gap rejected")
var empty := Accrual.new()
expect(fresh.restore_snapshot(empty.create_snapshot()), "unstarted state can restore")
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)

Some files were not shown because too many files have changed in this diff Show More