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
+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